Improving Turbo Frame UX with the busy Attribute.

Published on October 07, 2025
Written by Victor Cobos

When you drop a <turbo-frame> into your Rails app, the default experience is pretty bare. Click a link, and the frame quietly empties itself while waiting for the new content. For a second or two, the user just sees a blank box. Nothing is broken, but the silence feels awkward: is the app still working? did my click register?

That tiny gap is where UX can stumble. Without a loading indicator, users are left guessing what’s happening behind the scenes.

Luckily, Turbo gives us a built-in signal for this state. Whenever a is fetching new content, it automatically adds two attributes to the element:

  • busy – a simple attribute with no value
  • aria-busy="true" – an accessibility attribute used by assistive technologies

When the request finishes, Turbo removes busy and flips aria-busy back to false.

In other words, your frame is already telling the browser — and you — when it’s busy. All we need to do is style that state to give users a clear loading indicator. You can read more about the full list of frame attributes in the Turbo Frames documentation.

Styling the aria-busy state

Since Turbo keeps aria-busy up to date on every frame, that’s the attribute we’ll use for styling. The good news: Tailwind already includes aria-busy:* variants, so we can react to this state directly in our markup — no JavaScript needed.

In the examples below, I’ll use Tailwind utilities because they make it easy to demonstrate the concept. But the same idea works perfectly with plain CSS using the attribute selector:

turbo-frame[aria-busy="true"] {
    ...
}

There are two common patterns:
1. Showing an overlay inside the frame: group-aria-busy
2. Placing a loader as a sibling overlay peer-aria-busy

Inside the frame — using group-aria-busy

This approach places the loading indicator inside the <turbo-frame> itself.
We mark the frame as a group, which lets us style its children when it’s busy.
When Turbo sets aria-busy="true", the content dims and a spinner overlay fades in.

<turbo-frame class="group relative">
  <div class="hidden group-aria-busy:grid pointer-events-none absolute inset-0 place-items-center bg-white/60 backdrop-blur-sm rounded-lg">
    <div class="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
  </div>
</turbo-frame>

Turbo Frame

When the frame is idle, the overlay is hidden.
As soon as Turbo marks it aria-busy="true", the spinner appears and the content fades slightly to signal activity.

Outside the frame — using peer-aria-busy

Sometimes, you may want the loader outside the frame — for instance, if you don’t want to modify the frame’s internal markup or you want a larger overlay.

In this case, we wrap the frame in a container and mark the frame as a peer.
That lets sibling elements react to its aria-busy state using peer-aria-busy: utilities.

<div class="relative">
  <turbo-frame class="peer"></turbo-frame>
  <div class="hidden peer-aria-busy:grid pointer-events-none absolute inset-0 place-items-center bg-white/60 backdrop-blur-sm rounded-lg">
    <div class="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
  </div>
</div>

Turbo Frame

Here the overlay sits next to the frame, but still reacts to its busy state.
This pattern keeps your frame markup clean while still providing clear visual feedback.

Wrapping up

Turbo Frames make it easy to update parts of a page without a full reload, but that same magic can feel confusing when users don’t see what’s happening. The busy and aria-busy attributes give us a lightweight, built-in way to surface that hidden state — no custom JavaScript required.

Whether you show a subtle spinner, a blurred overlay, or a full skeleton loader, the idea is the same: keep users informed. A short visual cue turns a silent wait into a smoother, more trustworthy experience.

And while the examples here use Tailwind’s aria-busy variants for convenience, the same concept works anywhere you can write CSS. Turbo does the signaling; you decide how it looks.

References

Subscribe to get future articles via the RSS feed .