Spinner

Show that work is under way with an orbiting light, sized for icons, buttons, and empty states.

Loading results

Installation

You can add the spinner using our CLI or manually:

Dark mode ready - this component follows BoardUI semantic tokens automatically. Set up theme switching.

The CLI copies the component source (with its internal dependencies) into your project and installs any required npm packages.

npx boardui@latest add spinner

Using a shared UI package or custom import aliases? Configure your installation paths before running the CLI. Copy-paste examples use the default imports.

Anatomy

The light is drawn the way Composer Loader draws its band: the same arc at three scales, a crisp line with a rounded head, a tight glow, and a wide bloom. The soft layers trail a few degrees behind, so the sharp head always leads and the glow gathers behind it like the tail of a comet.

Five sizes from 12 to 32 pixels: xs, sm, md (the default), lg, and xl. A faint track completes the circle.

Colour

Every layer is drawn in the current text colour, accent by default. Set a text colour with className, and turn the track off with track={false}.

<Spinner size="lg" /> // accent<Spinner size="lg" className="text-text-secondary" /> // muted<Spinner size="lg" className="text-white" /> // on a primary surface<Spinner size="lg" track={false} /> // the light alone

In buttons

SpinnerGlyph is the bare light, for icon slots. Pass it as a Button's leading icon while a request is pending, and mark the button with aria-busy rather than disabling it, so it keeps its colour.

import { Button } from "@/components/base/buttons/button";import { SpinnerGlyph } from "@/components/base/spinner/spinner"; // Button's icon slot passes a size class; the glyph keeps its own box.function ButtonSpinner({ className }: { className?: string }) { return <SpinnerGlyph size="sm" className={cx(className, "text-current")} />;} // Busy, not disabled: the button keeps its colour, so the light stays legible.<Button leadingIcon={pending ? ButtonSpinner : undefined} aria-busy={pending} onClick={() => !pending && publish()}> {pending ? "Publishing" : "Publish changes"}</Button>

Accessibility

A Spinner is announced as an indeterminate progress bar named Loading. Rename it with aria-label, or hide it with aria-hidden where the surrounding control already says it is busy.

Reduced motion slows the orbit instead of stopping it, because the movement is what says the work is still going.