Spinner

An indeterminate ring for a wait with no known length. Sized in text units, so it fits wherever a glyph would.

API

Props

proptypedefault
as?ElementType"span"the element or component to render
label?string"Loading"the accessible name of the status
className?stringmerged over the defaults, so it wins

Examples

Usage

tsx
<Spinner />
<Spinner className="size-6 text-primary" />
preview

Details

Size and colour

The ring is an inline SVG drawn in currentColor and filling its box, so it takes the colour of the text around it and the size you give the element. The default is size-4, the same square a Button icon occupies, which is what lets a spinner replace an icon without the control changing width.

tsx
<Button clickable={!isSaving} onClickBlur={handleSave}>
  <Button.IconLabel
    icon={<Button.Icon icon={<Swap.Boolean swapOn={isSaving} components={[MdSave, Spinner]} />} />}
    label={<Button.Label label={isSaving ? "saving" : "save"} />}
  />
</Button>

What it announces

It renders as role="status" with an accessible name, so assistive technology reports it as a live status rather than as a decorative image. Rename it when the page has more than one wait, or when saying what is loading is useful.

tsx
<Spinner label="Loading invoices" />

Spinner, ProgressBar or NavigationBar

Three components for three different waits. A Spinner belongs where the wait is local and has no length — a button mid-request, a panel fetching its data. A ProgressBar is for a quantity you actually know, a file uploading or a step counter. A NavigationBar is for a page transition, where nothing is measurable but the whole screen is about to change.

The failure mode worth avoiding is a spinner that spins for something you could have measured: an indeterminate animation tells the user only that the app is not dead.