Indeterminate loading indicator for async work in buttons, forms, and inline content.
Description
Spinner is a single-element indeterminate loading indicator. It renders as a <div> with role="status" and a default aria-label="Loading". The arc is drawn entirely in CSS using masks and conic gradients, so there is no SVG payload to ship.
Use it whenever the app is doing work whose duration you can't predict: a save in flight, a network call resolving, a slow form field validating, a page bootstrapping. It composes inside Button via loading, inside InputGroup and Select as the slot icon, and stands alone next to inline status copy like "Saving…".
If you can show actual progress, use Progress so the user can pace their wait. If you're masking a layout shape while content loads (a card placeholder, an avatar slot), use Skeleton. For a static presence dot, use a presence-styled Badge. Spinner is for in-flight motion, not idle state.
Installation
Usage
Examples
Variants
variant sets the spinner's color. Twelve options cover the primary intent, status tones (info, success, warning, danger, away), the neutral set, and an inverse* set designed to read on dark surfaces.
Appearance
appearance picks the visual style. gradient (the default) fades from transparent into currentColor with no background track. solid draws a hard-edged arc over a visible track ring, which reads better at small sizes and on busy backgrounds.
Sizes
Four sizes scale the footprint and stroke width together: xs (10px), sm (13.33px, default), md (16.67px), and lg (20px). Use xs for inline-with-text status, lg for empty states and full-page loaders.
Animation
animation picks the rhythm. spin is a steady linear rotation, pulse (the default) rotates and breathes the arc length on the way around, and tick rotates fast then pauses to feel like discrete steps.
Cap
cap controls the end-cap geometry: sharp for a clean cut, rounded (the default) for soft pill caps. The cap only takes visual effect when appearance="solid"; on gradient the arc fades before reaching its tip, so the cap shape never shows.
Line
line sets how much of the circle the arc covers. short covers ~25% and feels lighter, long (the default) covers ~75% and reads as the more energetic loader silhouette.
Accessibility
Spinner is a passive status indicator. It is not focusable and there are no keyboard interactions to document. Keep the rendered text content brief: the component is announced via its aria-label, not its children.
ARIA notes:
- The component sets
role="status"automatically. Browsers and assistive tech treat it as a live region, so changes in surrounding labelled text are announced. - A default
aria-label="Loading"is set. Override it when the operation deserves a more specific label:<Spinner aria-label="Saving draft" />. - Pair the spinner with visible status copy ("Saving…", "Loading workspace") when the user needs to know what's happening. The label alone is brief by design.
- For decorative spinners where the parent control already announces its loading state (e.g. a
Buttonwithloading), addaria-hidden="true"to avoid duplicate announcements.
Styling
Tailwind override: the arc tracks currentColor, so the simplest way to recolor is a text-* utility merged via cn():
Data slots and attributes: the component sets these for CSS targeting:
data-slot="spinner"on the root<div>.data-slot="spinner-track"on the background track ring (rendered only whenappearance="solid").data-slot="spinner-arc"on the animated arc.data-slot="spinner-cap-head"anddata-slot="spinner-cap-tail"on the pill caps (rendered only whenappearance="solid"andcap="rounded").data-variant,data-appearance,data-cap,data-line,data-animation,data-sizeon the root.
Target a state in CSS:
Related Components
- Progress: use this when progress is determinate and you can show a percentage.
- Skeleton: use this when masking a layout shape while content loads, not for in-flight actions.
- Button: render the spinner inside via the
loadingprop instead of placing one next to the button manually.
API Reference
Spinner
Indeterminate loading indicator. Extends React.ComponentProps<"div">, so any standard div attribute (id, role, aria-*, onClick, etc.) is accepted. The component sets role="status" and a default aria-label="Loading"; override aria-label to describe the specific operation.