Skip to content
design
Components

Spinner

The ring that says an operation is running.

On this page
Saving the record
A wait, named: the ring that rotates while an operation runs, with the words a screen reader hears.
Code
import { Spinner } from "@bosunkit/components/spinner";
import type { ReactElement } from "react";

export const description: string =
  "A wait named for the crew: the ring, and what is loading, so a screen reader hears the same.";

export default function Saving(): ReactElement {
  return <Spinner label="Saving the record" />;
}

Spinner is the ring that says work is in progress. It appears once an operation has run for more than a second when the caller applies that delay: a fast operation can finish without showing one. The motion rules allow exactly two loops — the alert flash, and this ring — so a spinner reads as progress, never as an alarm.

Named or silent. With a label, the spinner is a status region: the words under the ring are what a screen reader announces. Without one it says nothing, and the caller names it — the text beside a button's pending spinner does. Under reduced motion the ring does not turn; the wait is the words.

Sizes

LoadingLoadingLoading
Three sizes on the size classes: a spinner beside text takes the text's size, and the prop only matters on its own.
Code
import { Spinner } from "@bosunkit/components/spinner";
import type { ReactElement } from "react";

export const description: string =
  "Three sizes on the size classes: a spinner beside text takes the text's size, and the prop only matters on its own.";

export default function Sizes(): ReactElement {
  return (
    <div style={{ display: "flex", gap: "var(--space-md)", alignItems: "center" }}>
      <Spinner size="sm" label="Loading" />
      <Spinner size="md" label="Loading" />
      <Spinner size="lg" label="Loading" />
    </div>
  );
}

The sizes are the size classes in em, so a spinner beside a button's pending label takes the label's size, and the prop only matters where there is no text around to inherit from.

The caller owns the wait

Spinner renders immediately when mounted; the caller decides when an operation has waited long enough to show it. It does not start work, measure progress, cancel a request or enforce a timeout. Name the operation with label, or provide visible pending text in the containing control without a second announcement.

Use a skeleton when the final content's structure is known. Keep an error and recovery path for a failed or unusually long operation; an endless spinner is not a useful error state. Retain confirmed data while refreshing when it can be labeled accurately, and do not use a spinner to disguise an unavailable instrument value.

Props

PropTypeDefaultDescription
labelstring | undefined–What is loading, in a few words ("Finding the position"). Shown under the ring and announced through the spinner's status region, so the wait reaches a screen reader. Unset, the spinner is silent: the caller names it, as the text beside a button's pending spinner does.
sizeSpinnerSize | undefined"md"How big the ring is: sm, md or lg; md unless set.
classNamestring | undefined–Classes added to the spinner's own.
testIdstring | undefined–Becomes data-testid on the spinner's root.