Skip to main content
Use aria-busy="true" on any element to show a loading spinner. Size with data-spinner attribute.

Basic Usage

<div aria-busy="true"></div>

Sizes

<div aria-busy="true" data-spinner="small"></div>

Multiple Sizes Example

<div class="hstack" style="gap: var(--space-8)">
  <div aria-busy="true" data-spinner="small"></div>
  <div aria-busy="true"></div>
  <div aria-busy="true" data-spinner="large"></div>
</div>

In Buttons

Spinners work inside buttons for loading states:
<button aria-busy="true" data-spinner="small" disabled>
  Loading
</button>
Always disable buttons with disabled attribute when showing a loading spinner to prevent multiple submissions.

Overlay Spinner

Add data-spinner="overlay" to dim the container contents and overlay the spinner on top:
<article class="card" aria-busy="true" data-spinner="large overlay">
  <header>
    <h3>Card Title</h3>
    <p>Card description goes here.</p>
  </header>
  <p>This is the card content. It can contain any HTML.</p>
  <footer class="flex gap-2 mt-4">
    <button class="outline">Cancel</button>
    <button>Save</button>
  </footer>
</article>

Overlay Behavior

  • Dims all child elements to 30% opacity
  • Disables pointer events on children
  • Centers spinner with absolute positioning
  • Works with any container size

Dynamic Loading States

Toggle loading state with JavaScript:
const button = document.querySelector('button');

// Start loading
button.setAttribute('aria-busy', 'true');
button.setAttribute('data-spinner', 'small');
button.disabled = true;

// End loading
button.removeAttribute('aria-busy');
button.removeAttribute('data-spinner');
button.disabled = false;

With Fetch Example

const container = document.querySelector('#content');

async function loadData() {
  container.setAttribute('aria-busy', 'true');
  container.setAttribute('data-spinner', 'overlay');
  
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    // Update UI with data
  } finally {
    container.removeAttribute('aria-busy');
    container.removeAttribute('data-spinner');
  }
}

Styling

Spinners include:
  • Circular rotating animation
  • Primary color accent
  • Muted background color
  • Smooth 1-second rotation
  • Three size variants (small, default, large)

Accessibility

The aria-busy="true" attribute:
  • Announces loading state to screen readers
  • Indicates the element is being updated
  • Works with all assistive technologies
For better accessibility, consider adding aria-label:
<div aria-busy="true" aria-label="Loading content"></div>
For skeleton loading placeholders, see the Skeleton component.