Spinner

Indeterminate wait indicator — one recipe: a border.medium ring in currentColor, open at the top, turning at 3× motion.duration.slow. It has no color of its own and sits on the icon scale, so it drops into text rows, badges, and controls without new bindings.

Sizes

One sm/md/lg dialect on the shared icon scale; the default is medium.

ExampleSizeModifierBoxUsage
Small--smsize.icon.smInline with text, dense rows
Mediumds-spinnersize.icon.mdThe default — panels and regions
Large--lgsize.icon.lgFeature areas, empty states

Contexts

currentColor is the whole color contract: the spinner takes the text color of wherever it sits. What changes per context is the wiring — whether the spinner announces itself or defers to visible text.

ExampleContextWiringInheritsUsage
Standalonerole="status"aria-labeltext.primaryA region awaiting content announces itself
Loading previewBeside textaria-hiddentext.primaryThe text is the announcement — one voice, not two
SyncingIn a badgearia-hiddenstatus.info.fgThe badge's fg role colors the ring
In a buttonaria-busyaction.secondary.textThe button ships its own busy spinner — never hand-place one

Usage notes

A spinner says "wait, duration unknown" — and only that.

VerdictRule
DoAnnounce a standalone spinner with role="status" plus aria-label; beside visible text it goes aria-hidden — the text carries the update.
DoSet aria-busy="true" on a busy button and let it bring its own spinner — a hand-placed one loses the padding, icon, and announcement rules Button already contracts.
Don’tUse a spinner for measurable progress — that is Progress; a spinner only says wait.
Don’tColor a spinner directly — recolor its context and currentColor follows.