Skip to content

Spinner

  • A button busy saving: “Saving…”
  • A small area refreshing
  • Short waits with no known end

Not for a task with a known total — use a Progress — or loading a whole layout — use a Skeleton.

Choosing between showing progress, loading or a measurement:

ComponentUse it forFor example
ProgressA task whose completion is known, moving toward doneUpload at 40%, step 2 of 5
StepperThe named steps of a multi-step flow, and which one you’re onCheckout: Cart, Shipping, Payment
MeterA measurement within a known range, not a taskDisk usage, password strength, a score
SpinnerWork of unknown length, in a small spaceA button saying “Saving…”, a panel refreshing
SkeletonContent still loading whose shape is known, so the layout does not jumpPlaceholder rows in a list, a profile card
import { Button, Spinner } from '@arun-dev/ui';
export default function SpinnerBasics() {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-md)' }}>
<Spinner />
<Spinner aria-label="Saving draft" style={{ ['--spinner-size' as string]: '2rem' }} />
{/* In a button, the text already says what is happening: hide the spinner. */}
<Button disabled>
<Spinner aria-hidden="true" style={{ ['--spinner-size' as string]: '1rem' }} /> Saving…
</Button>
</div>
);
}

Spinner is a native <progress> with no value, drawn as a turning ring. Screen readers hear a progress bar named “Loading”. Give it an aria-label that says more when you can, such as “Saving draft”. Under reduced motion it turns slowly instead of stopping, so it still looks busy.

When the text beside it already says what is happening, as in a “Saving…” button, hide it with aria-hidden="true".

When you know how far along the task is, use a Progress.

Spinner props
PropTypeDefaultDescription
aria-labelstring'Loading'What is loading. Say more than the default when you can.
classNamestringConcatenated with the component's own classes, never replacing them.
renderReactElementElement or component to render instead of the default. Props, className, event handlers and ref are merged onto it.
refRef<HTMLElement>Ref to the rendered element. Merged with any ref on the render element.
…restHTMLAttributesAnything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM.
  • Size it with --spinner-size. Its colours come from --spinner-color and --spinner-track-color.