Feedback/Spinner

Spinner

A loading spinner indicator.

Themed

When to Use

Use Spinner when you need to:

  • Indicate an indeterminate loading state for async operations
  • Provide visual feedback inside buttons during form submissions
  • Show inline status indicators in badges or list items
  • Signal background processes like data syncing or file uploads
  • Replace content placeholders when load time is brief (under 3 seconds)

When Not to Use

  • Determinate progress with known completion - use Progress
  • Long-loading content layouts - use Skeleton for perceived performance
  • Page-level loading states - consider a full-page skeleton or suspense boundary
  • Operations longer than 10 seconds without progress - add a progress bar or status text

Default

Basic spinner with loading text. Uses Loader2 icon with animate-spin at 16px.

Loading...

Sizes

Use Tailwind size-* utility classes to adjust the spinner dimensions. Default is size-4 (16px).

Colors

Spinner inherits color from its parent via currentColor. Use text-* classes to override.

In Buttons

Add a spinner to disabled buttons to indicate a loading or processing state.

In Badges

Use a smaller spinner inside badges for inline status indicators.

SyncingUpdatingProcessing

UX & Design Guidelines

Visual Hierarchy

Pair spinners with descriptive text (e.g., "Loading...", "Saving changes") so users understand what is happening. Use text-muted-foreground for secondary loading indicators and text-primary when the spinner is the main focus of attention.

Sizing & Proportion

Match spinner size to its context: size-3 inside badges and compact UI, size-4 (default) inside buttons and inline text, size-6 or size-8 for card or section-level loading states. Avoid oversized spinners that dominate the layout.

Timing & Perception

Show the spinner immediately for operations expected to take over 300ms. For operations under 300ms, consider skipping the spinner to avoid flicker. If loading exceeds 3-5 seconds, supplement with a text message explaining the delay. For operations longer than 10 seconds, switch to a Progress bar with determinate feedback.

Color & Contrast

The spinner inherits color via currentColor, adapting automatically to its parent context. Prefer semantic tokens like text-foreground, text-muted-foreground, or text-primary over hardcoded color values. Ensure the spinner maintains at least 3:1 contrast ratio against its background in both light and dark modes.

Accessibility

Keyboard Navigation

  • Spinners are non-interactive and do not receive keyboard focus
  • When a spinner is inside a Button, the button should be disabled to prevent repeated submissions
  • Tab will skip the spinner and move to the next focusable element
  • Escape should cancel the pending operation when applicable

Screen Reader Support

  • Uses role="status" to announce loading state to assistive technology
  • Has aria-label="Loading" by default for a clear screen reader announcement
  • Override aria-label for context-specific messages (e.g., "Uploading file")
  • When paired with visible text, set aria-hidden="true" on the spinner to avoid duplicate announcements

Live Regions

  • Wrap loading areas with aria-live="polite" to announce state changes
  • Use aria-busy="true" on the container while content is loading
  • Screen readers will announce when loading completes and content replaces the spinner

Motion Sensitivity

  • The animate-spin animation respects prefers-reduced-motion media query
  • Users with motion sensitivity will see a static or subtly pulsing indicator instead
  • Always provide text alongside the spinner so meaning is not lost when animation is suppressed

ARIA Attributes

{/* Basic spinner with implicit role="status" and aria-label */}
<Spinner />

{/* Custom aria-label for specific context */}
<Spinner aria-label="Uploading file" />

{/* Spinner paired with visible loading text */}
<div role="status" className="flex items-center gap-2">
  <Spinner aria-hidden="true" />
  <span>Loading results...</span>
</div>

{/* Live region announcing loading state */}
<div aria-live="polite" aria-busy={isLoading}>
  {isLoading ? (
    <div className="flex items-center gap-2">
      <Spinner aria-hidden="true" />
      <span>Fetching data...</span>
    </div>
  ) : (
    <div>{content}</div>
  )}
</div>

API Reference

The Spinner component accepts the following props in addition to standard SVG attributes.

PropTypeDefaultDescription
classNamestringAdditional CSS classes for sizing (size-*) and color (text-*).
rolestring"status"ARIA role for the spinner element. Defaults to status for screen reader announcements.
aria-labelstring"Loading"Accessible label for screen readers. Override for context-specific loading messages.