Spinner
When to use
Section titled “When to use”- A button busy saving: “Saving…”
- A small area refreshing
- Short waits with no known end
Not for a task with a known total — use a Progress — or loading a whole layout — use a Skeleton.
Choosing between showing progress, loading or a measurement:
| Component | Use it for | For example |
|---|---|---|
| Progress | A task whose completion is known, moving toward done | Upload at 40%, step 2 of 5 |
| Stepper | The named steps of a multi-step flow, and which one you’re on | Checkout: Cart, Shipping, Payment |
| Meter | A measurement within a known range, not a task | Disk usage, password strength, a score |
| Spinner | Work of unknown length, in a small space | A button saying “Saving…”, a panel refreshing |
| Skeleton | Content still loading whose shape is known, so the layout does not jump | Placeholder rows in a list, a profile card |
import { Button, Spinner } from '@arun-dev/ui';
export default function SpinnerBasics() { return ( <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-md)' }}> <Spinner /> <Spinner aria-label="Saving draft" style={{ ['--spinner-size' as string]: '2rem' }} /> {/* In a button, the text already says what is happening: hide the spinner. */} <Button disabled> <Spinner aria-hidden="true" style={{ ['--spinner-size' as string]: '1rem' }} /> Saving… </Button> </div> );}Spinner is a native <progress> with no value, drawn as a turning ring. Screen readers hear
a progress bar named “Loading”. Give it an aria-label that says more when you can, such as
“Saving draft”. Under reduced motion it turns slowly instead of stopping, so it still looks
busy.
When the text beside it already says what is happening, as in a “Saving…” button, hide it with
aria-hidden="true".
When you know how far along the task is, use a Progress.
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | 'Loading' | What is loading. Say more than the default when you can. |
className | string | Concatenated with the component's own classes, never replacing them. | |
render | ReactElement | Element or component to render instead of the default. Props, className, event handlers and ref are merged onto it. | |
ref | Ref<HTMLElement> | Ref to the rendered element. Merged with any ref on the render element. | |
…rest | HTMLAttributes | Anything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM. |
- Size it with
--spinner-size. Its colours come from--spinner-colorand--spinner-track-color.