# Spinner
The one “working” indicator — a ring, never a shimmer.
- Group: Feedback
- Import: `import { Spinner } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/spinner/
- Related: skeleton, progress
## When to reach for it
A wait short enough that the shape of what is coming does not matter. Longer than that, use a Skeleton.
## Anatomy
- **Live region** (required) — The outer span, and the only part with a voice. It carries role="status" while there is a label; pass label={null} and it turns into an aria-hidden box with no role at all.
- **Ring** (required) — The inner span — 14px, 18px or 26px of border on a transparent box, and the only element size, tone and className reach.
- **Leading quarter** (required) — border-t, drawn in --ink at the default tone and in the inherited colour at current. It is the whole difference between a ring and a plain circle, which is why a still ring still reads as unfinished.
- **Screen-reader label** — An sr-only span holding label, present unless label is null. Announced once, when the spinner mounts, and never again.
## Best practices
### Do
- Pass tone="current" for a spinner on any filled ground: the default draws the leading quarter in --ink over a --rule-2 track, and inside a primary Button both of those are the ground it is sitting on.
- Announce the arrival somewhere else. The label is read once on mount and nothing is said on the way out, so a reader who heard “Loading projects” is never told the projects came.
- Reach for label={null} only inside a control that already names the operation — it hides the whole element from assistive tech rather than merely dropping the text, so a silenced spinner standing on its own is a wait nobody is told about.
### Don’t
- label defaults to the bare word “Loading”, so a Spinner written without the prop ships the exact announcement the prop exists to prevent — the default is a placeholder, not a value.
- className is merged onto the ring AFTER size and tone, so it beats both: is a 16px ring, and the prop whose whole job was naming the size is the one that lost.
- Nothing here sets aria-busy — Button does that for its own control — so a spinner laid over a panel leaves the panel announced as ready while its contents are stale and its buttons still take clicks.
## Accessibility
- label names the specific thing being waited on; three spinners all saying “Loading” tell a screen reader nothing.
- label={null} silences it for use inside a control that already announces the operation.
- Spins under motion-safe only; the static ring still reads as unfinished because the leading quarter is darker.
## Spinner
The system's one "working" indicator. A ring, not a blurred glow and not a shimmer: the White Reset has no light source, so depth and softness are not available to it. The leading quarter is the only thing that distinguishes the ring from a plain circle, which is why the track stays a hairline — a spinner is a hint that time is passing, not a feature of the page. It spins under `motion-safe` only. A reader who has asked for less motion gets a static ring, which still reads as "not finished" because the leading quarter is darker than the track. For a wait long enough that the reader would otherwise wonder whether the page is broken, prefer `Skeleton` — a shape that describes what is coming beats a dot that describes nothing. `className` reaches the RING, alongside `size` and `tone`, and overrides them: every utility a caller has for a spinner is about the ring, and merged onto the wrapper instead `className="size-8"` grew an invisible box around an unchanged 18px circle. Layout still works from there — the wrapper is `inline-flex` and takes the ring's margin box as its own.
### Props
- `size` — `'sm' | 'md' | 'lg'` default `'md'`. 14px / 18px / 26px. Match the size of the type it sits beside.
- `tone` — `'default' | 'current'` default `'default'`. `default` draws the ring against the page — a hairline track in the rule colour with the leading quarter in ink. `current` draws it in the inherited text colour, which is what a spinner inside a filled button needs: on an ink ground an ink ring is invisible.
- `className` — `string`. Merged onto the RING, after `size` and `tone`, so it overrides both.
- `label` — `string | null` default `'Loading'`. Announced to assistive tech. Pass the specific thing being waited on ("Loading projects"), not the generic word — a screen reader user hearing "Loading" three times cannot tell which three things. Pass `null` for a spinner that sits inside a control which already names the operation (a button whose own label changes to "Saving…"), so the two are not read out twice.
Also accepts: `HTMLAttributes`.
## Example — sizes
```tsx
import { Spinner } from '@misoto22/folio'
```
## Example — on a filled ground
```tsx
import { Button, Spinner } from '@misoto22/folio'