Spinner
An animated circular progress indicator. Use it standalone or compose it inside Button and IconButton for loading states.
Overview
Spinner is a pure SVG component — no external dependencies. The size prop is a pixel number that controls width and height. The stroke color inherits via stroke-current so Spinner picks up whatever color or text-* class is on a parent element.
Button and IconButton internally use Spinner for their loading state via setupButtonSpinner, which automatically scales size and thickness for the button's current size prop.
Import
import { Spinner } from '@unflow/ui/components/Spinner';Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | number | 32 | Diameter in pixels |
thickness | number | 2 | Stroke width in pixels |
speed | 'slow' | 'normal' | 'fast' | 'normal' | Animation speed |
All standard HTMLDivElement props are forwarded to the wrapper <div role="status">.
Default
<Spinner aria-label="Loading" />Sizes
<Spinner size={16} aria-label="Loading" />
<Spinner size={24} aria-label="Loading" />
<Spinner size={32} aria-label="Loading" />
<Spinner size={48} aria-label="Loading" />
<Spinner size={64} aria-label="Loading" />Speeds
<Spinner speed="slow" aria-label="Loading slowly" />
<Spinner speed="normal" aria-label="Loading" />
<Spinner speed="fast" aria-label="Loading quickly" />| Speed | Duration |
|---|---|
slow | 2.75s |
normal | 1.5s |
fast | 0.75s |
Color
Spinner inherits its stroke color from the CSS color property via stroke-current. Override by setting a text-* class on the Spinner or a parent:
<Spinner className="text-indigo-500" aria-label="Loading" />
<Spinner className="text-red-500" aria-label="Loading" />
{/* or inherit from parent */}
<div className="text-brand-500">
<Spinner aria-label="Loading" />
</div>Inside Button
<Button label="Save changes" loading />Button handles Spinner sizing automatically based on its size prop. Use slotProps.loading.spinner to override:
<Button
label="Save"
loading
slotProps={{ loading: { spinner: { size: 14, thickness: 1.5 } } }}
/>Accessibility
- Renders
<div role="status">— screen readers announce it as a live region. - Always provide
aria-labelso the announcement is meaningful:aria-label="Loading search results"not just"Loading". - Remove from the DOM (or use
aria-hidden) when loading is complete.