Spinner
An indeterminate busy indicator. A ring drawn from a border and one CSS rotation, so it scales with the text beside it and ships no JavaScript at any tier.
Example (a11y tier)
Import
import Spinner from 'abaabil/spinner' // structure onlyimport Spinner from 'abaabil/spinner/styled' // + CSSimport Spinner from 'abaabil/spinner/a11y' // + role=status, labelA ring says nothing out loud
The a11y tier wraps the ring in role="status", which is an implicit polite live region: the label is read when the spinner appears, without interrupting whatever is being read at the time. That is the right politeness for loading, which the user usually caused and is already expecting.
The name is visually hidden text, not aria-label. A <span> is a generic element and aria-label is prohibited on one: set it there and the browser discards it, leaving the spinner as unnamed as it was while looking fixed. That mistake shipped in this library's own popover once.
Pass decorative for a spinner inside a button that already says “Saving…”. Two announcements of one fact is worse than one.
It does not freeze under reduced motion
Asking for less motion is not asking to be told less. Under prefers-reduced-motion the ring stops rotating and pulses its opacity instead, which is not vestibular motion but still says work is in progress. A frozen ring reads as a component that failed to start.
Skeleton does the opposite and simply holds still, because it communicates by occupying the space the content will, and loses nothing by not moving.
The ring uses the track token, and that was a bug first
The unfilled part of the ring is --color-track, the same token progress and slider use for the empty part of a bar. The first version used --color-border instead, which is 1.65:1 against the accent in light and 1.13:1 in dark: the component span and looked like a static circle. It was caught by adding the pairing to the library's contrast test before release, not after.
Props
| Prop | Type | Default | Tier | Description |
|---|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | all | Applied as data-size. Scales with font size. |
label | string | 'Loading' | a11y | Announced when the spinner appears. |
decorative | boolean | false | a11y | Hides it, for a spinner beside its own caption. |
className | string | — | all | Merged with the base class. |