Spinner
A loading spinner indicator.
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
Default
Basic spinner with loading text. Uses Loader2 icon with animate-spin at 16px.
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.
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
disabledto 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-labelfor 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-spinanimation respectsprefers-reduced-motionmedia 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>Related Components
API Reference
The Spinner component accepts the following props in addition to standard SVG attributes.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes for sizing (size-*) and color (text-*). |
| role | string | "status" | ARIA role for the spinner element. Defaults to status for screen reader announcements. |
| aria-label | string | "Loading" | Accessible label for screen readers. Override for context-specific loading messages. |