Spinner

A spinning loading indicator for indeterminate async operations. Built as an SVG component using Loader2Icon from lucide-react with animate-spin, role="status", and a built-in accessible label.

Anatomy

A single <Spinner> SVG element with role="status" and aria-label="Loading". Default size is size-4. Override via className.

role="status"aria-label="Loading"animate-spinsize-4 default

Sizes

Override size via a Tailwind size-* className. Use the smallest size that remains legible in context.

size-4

Inline, buttons

size-6

Cards, panels

size-8

Full-page overlays

In Context

The most common usage patterns — loading buttons and inline text indicators.

Loading button

Inline text

Syncing changes…

Generating report…

Centered in container

Muted color

Background task running

Design Guidelines

Do

  • Use for unknown durations. Spinner is best when you can't measure completion — network requests, AI generation, async operations.
  • Pair with disabled state on triggers. Disable the button or form that initiated the action while the spinner is visible.
  • Add a label in context. "Saving…" or "Loading" beside the spinner prevents ambiguity.

Don't

  • Don't use when progress is quantifiable. If you know the percentage, use Progress instead — it gives users more useful feedback.
  • Don't show spinner for instant operations. Under ~200ms, a spinner causes more confusion than it prevents.
  • Don't spin indefinitely without a timeout. After a reasonable threshold, show an error state with a retry option.

Developer Reference

Accessibility

  • role="status" announces to screen readers that a loading state is active.
  • Built-in aria-label="Loading" gives the SVG an accessible name without extra markup.
  • When used inside a button, pair with disabled and add visible or sr-onlytext to describe the action (e.g., "Saving…").
  • Animation uses animate-spin — respects prefers-reduced-motionvia Tailwind's motion utilities.

Usage

import { Spinner } from "@/components/ui/spinner"

// Default (size-4)
<Spinner />

// Custom sizes
<Spinner className="size-6" />
<Spinner className="size-8" />

// Loading button
<Button disabled>
  <Spinner />
  Saving…
</Button>

// Inline text
<p className="flex items-center gap-2">
  <Spinner className="size-3.5" />
  Syncing changes…
</p>

// Centered in a container
<div className="flex h-40 items-center justify-center">
  <Spinner className="size-6" />
</div>