Hotwire + Tailwind: Spinner without Javascript

by Elvinas Predkelis

May 22, 2022

Idea and Setup

The setup is pretty straight-forward. It’s just a turbo frame with a spinner inside of it.

However, when the frame is being loaded, it looks something like <turbo-frame id="loadable" busy="" aria-busy="true"> - it has a [busy] attribute attached to it.

Turns out, it’s possible to leverage that [busy] attribute. 💆

Adding Tailwind modifiers

This is where most of the 🪄🎩 magic 🎩🪄 happens. Tailwind allows to set up custom modifiers which is exactly what we need in this case.

// tailwind.config.js

let plugin = require("tailwindcss/plugin")
module.exports = {
  // ...
  plugins: [
    plugin(({ addVariant }) => {
      addVariant("busy", "&[busy]")
      addVariant('group-busy', ':merge(.group)[busy] &')

Here a busy modifier is set up which is bound to the [busy] attribute. Also, group-busy is added to target the spinner inside of the parent turbo frame.

Finishing touches

Now we can just ✨sprinkle✨ some classes on some elements.

<turbo-frame id="loadable" class="group">
    class="group-busy:inline hidden h-6 w-6 animate-spin"
    viewBox="0 0 24 24"
    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
      d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"

In this instance, the spinner just appears while the turbo frame is being loaded. The frame element now has a group class while the spinner now has group-busy:inline hidden classes.

Obviously, you can now style it to your liking when using the respective modifiers. For example, busy:opacity-50 will make the turbo frame transparent while loading.

Wrapping up

The main goal was to once again shake off some unnecessary Javascript from the codebase. This turned out to be a rather tidy way of doing so while keeping the simplicity of Hotwire and the flexibility of Tailwind.