abaabil

Spinner

4th lightest of the thirty-two, by js weight

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)

Loading, smallLoading resultsLoading, large

Import

import Spinner from 'abaabil/spinner'         // structure only
import Spinner from 'abaabil/spinner/styled'  // + CSS
import Spinner from 'abaabil/spinner/a11y'    // + role=status, label

A 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

PropTypeDefaultTierDescription
size'sm' | 'md' | 'lg''md'allApplied as data-size. Scales with font size.
labelstring'Loading'a11yAnnounced when the spinner appears.
decorativebooleanfalsea11yHides it, for a spinner beside its own caption.
classNamestringallMerged with the base class.