Spinner
An inline loading indicator for asynchronous operations. Uses a CSS animation so it works without JavaScript and never blocks the main thread.
Preview
Loading…Loading…Loading…Loading…
Sizes
| Size | Diameter | Stroke width | Typical use |
|---|---|---|---|
| xs | 12px | 3px | Inside buttons or badges |
| sm | 16px | 2.5px | Inline text, compact rows |
| md | 24px | 2.5px | Default — cards, panels |
| lg | 32px | 2px | Full-section loading states |
Inline usage
Saving changes…Saving changes…
Loading…Syncing
Implementation
Spinner.tsx
tsx
import { Spinner } from "@/components/ui/Spinner";
// Default
<Spinner />
// Sizes
<Spinner size="xs" />
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />
// Custom accessible label
<Spinner label="Saving changes…" />
// Inline within text
<div className="flex items-center gap-2">
<Spinner size="sm" />
<span>Loading results…</span>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "md" | Controls the spinner diameter and stroke width. |
label | string | "Loading…" | Accessible label announced by screen readers via aria-label and a visually-hidden <span>. |
className | string | — | Additional classes applied to the outer <span> wrapper. |
Accessibility
- →The wrapper uses role="status" so screen readers announce when loading begins.
- →The SVG is aria-hidden; the accessible text is provided by a visually-hidden <span> — both the aria-label and the sr-only text are used for maximum compatibility.
- →Provide a descriptive label prop when the context is not obvious (e.g. "Saving changes…" vs the default "Loading…").
- →For page-level loading states, consider aria-live="polite" on the container so users are notified when content becomes available.
- →Do not use Spinner as the sole loading indicator inside a button that already handles its own loading state — prefer the Button component's built-in loading prop.