Components
Spinner
An indeterminate loading indicator for waits of unknown length. A brand arc rotates on a neutral track, spinning continuously until the wait ends.
Smooth by construction. The arc spins with a single linear rotation from 0 to 360 degrees, so the loop closes on itself with no seam or stutter. The arc maps to background-primary and the track to border-default, so both remap in dark mode.
Preview
Rendered with live Cognition tokens. Toggle the theme and the arc and track remap, no dark: classes.
Variants
<Spinner size="sm" /><Spinner /><Spinner size="lg" />Three sizes at 16, 24, and 32 pixels. Stroke weight scales with the diameter.
States
Spinning. The single, perpetual state until the wait resolves.
Inline beside a label.
Inside a disabled button.
The spinner is indeterminate, so it has one continuous state. Pair it with text or a control to signal what is loading.
API
Sets role="status" with an aria-label so the wait is announced.
Don't and Do
Don't reach for a spinner when the final layout is already known. A spinner leaves the reader staring at a blank region and then jolts them with a content shift. Where the shape of the result is predictable, a Skeleton holds the space and reads as calmer.
<Spinner />
<Spinner size="sm" /> // inline, e.g. in a button
<Spinner size="lg" />Use it for waits of unknown length where there is no layout to preview, such as a submit in flight or a background task finishing.
import { Spinner } from "@/components/ui/spinner";
export function SavingState() {
return (
<div className="flex items-center gap-2 text-sm text-text-subtle">
<Spinner size="sm" />
Saving changes...
</div>
);
}