Spinner
A spinner shows that something is loading without saying how far along it
is. It is the same ring Button and Combobox draw while busy, wrapped in
a <span role="status"> with visually hidden text so assistive technology
can read the busy state. It draws with currentColor, so any text colour
utility colours it, and stops spinning under prefers-reduced-motion.
import Spinner from '@zuilib/components/spinner'
For the bare icon (no live region, aria-hidden), use
@zuilib/components/lib/spinner instead.
Sizes
sm, md and lg follow the shared icon scale (size-4/5/6), so a
spinner sits beside text at the same size as a chevron or check mark. xl
(size-8) is for a page or panel level state. A consumer size-*, w-*
or h-* on the root replaces the preset and the ring follows.
Colour
The ring is currentColor. Set a text colour on the root or inherit one
from the surrounding text.
Announcing the busy state
Screen readers announce changes inside a live region that is already in
the document, not a region that arrives together with its text. Keep the
spinner mounted and toggle its label, or let the host carry the state
(aria-busy on the panel, or a Button's loading, which announces
itself). aria-label on the root is not live content and is never
announced.
Props
SpinnerProps extends the <span> attributes (minus children) with:
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | size-4/5/6 on the icon scale; xl is size-8. Exposed as data-size |
label | string | 'Loading' | Visually hidden text inside the live region. Pass '' when the host already names the state, leaving only the ring |
className | string | — | Merged last onto the root. A size-* / w-* / h-* here overrides the preset; a text colour colours the ring |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="spinner"] | span | Root, role="status". Also data-size |
[data-slot="spinner-icon"] | svg | The ring, size-full, drawn in currentColor; aria-hidden |
[data-slot="spinner-label"] | span | The visually hidden label; absent when label="" |
/* Consumer CSS: brand-coloured spinners everywhere */
[data-slot="spinner"] {
color: var(--primary);
}
Tokens
| Token | Used for |
|---|---|
--spacing | The size presets (size-4/5/6/8) |
--duration-* | Not used; the ring's rotation is a fixed keyframe that prefers-reduced-motion stops |
Accessibility
- The root is
role="status"(an implicit polite live region) holding the hidden label; the SVG isaria-hidden. - Announcements happen on change: mount the spinner ahead of time and swap
the
label, or setaria-busyon the host instead. - Under
prefers-reduced-motionthe ring stops and stays as a static arc. - Nothing is focusable; the spinner is static.