Mosmox UI
Current version
GitHubnpmjs.com
Feedback & Status

Spinner

An accessible loading and progress indicator with multiple visual modes, sizes, tones and layout options.

Spinner communicates that work is in progress. It supports indeterminate loading and determinate progress, and can render inline, with a label, over a container, or over the whole viewport.

Open the interactive Spinner examples on the component showcase →

Basic usage

Without value, Spinner is indeterminate and uses role="status". The default visual is a primary ring.

<Spinner />
<Spinner label="Loading" />
<Spinner aria-label="Checking for updates" />

Use a visible label when the state needs to be explained. For an icon-only indicator, provide an explicit aria-label.

Visual variants

The variant prop controls the animated visual:

  • ring — circular arc with an optional background track; default.
  • dots — three animated dots.
  • bars — four animated vertical bars.
  • pulse — a pulsing circle.
<Spinner variant="ring" label="Loading" />
<Spinner variant="dots" label="Fetching data" />
<Spinner variant="bars" label="Syncing" />
<Spinner variant="pulse" label="Connecting" />

track affects the ring variant. Set track={false} to show only the active arc. It has no visual effect on dots, bars or pulse.

<Spinner variant="ring" track={false} aria-label="Loading" />

Sizes and tones

size accepts xs, sm, default, lg and xl. The default visual is 22px; the other sizes are 14px, 18px, 30px and 44px.

tone accepts primary, secondary, success, danger, light and inherit.

<Spinner size="xs" tone="secondary" />
<Spinner size="sm" tone="success" />
<Spinner tone="primary" />
<Spinner size="lg" tone="danger" />
<Spinner size="xl" tone="inherit" />
<Spinner tone="light" />

Use tone="light" on a dark surface. tone="inherit" takes the current text color from its parent.

Labels and layout

label accepts any ReactNode. The default labelPosition="inline" places it beside the visual; labelPosition="below" stacks it underneath.

<Spinner label="Saving changes" />
<Spinner label={<span>Uploading files…</span>} labelPosition="below" />

Determinate progress

Passing value switches Spinner to determinate mode. It then uses role="progressbar" and exposes aria-valuemin, aria-valuemax and aria-valuenow.

The default range is 0..100. min and max define an inclusive custom range, and the rendered value is clamped to that range.

<Spinner value={68} label="Uploading 68%" />
<Spinner value={3} min={0} max={5} label="Step 3 of 5" />
<Spinner value={10} min={10} max={20} label="Starting" />

value={0} is determinate and represents the start of the range. A value equal to max is complete; then aria-busy becomes false. While indeterminate, or while determinate progress is below 100%, aria-busy is true.

The current progress is also exposed as the CSS custom property --mm-spinner-progress in percentage form. The other visual variants retain their animation while exposing the same progress accessibility state.

Animation speed and ring thickness

speed is the animation duration in seconds. Values below 0.1 are normalized to 0.1. thickness is the ring border width in pixels; values below 1 are normalized to 1. Thickness is meaningful for variant="ring".

<Spinner speed={1.5} label="Slow background sync" />
<Spinner speed={0.35} label="Fast validation" />
<Spinner variant="ring" thickness={2} />
<Spinner variant="ring" thickness={5} size="lg" />

The component respects prefers-reduced-motion by slowing animations automatically.

Container and fullscreen overlays

overlay positions Spinner over the nearest positioned ancestor. The parent should use position: relative (or another positioned layout).

<div style={{ minHeight: 120, position: 'relative' }}>
  <Spinner overlay label="Loading panel" />
  <PanelContent />
</div>

fullscreen creates a fixed viewport overlay for application-level loading. Use it sparingly because it covers the whole interface visually.

<Spinner fullscreen label="Loading application" />

Both options work with every variant, size, tone and label position.

Controlled updates

Spinner is presentational: update value from the parent when progress changes. Omitting value keeps it indeterminate.

const [progress, setProgress] = useState(0);

<Spinner value={progress} label={`${progress}% complete`} />

Accessibility

  • Indeterminate Spinner uses role="status" and defaults to aria-label="Loading".
  • Determinate Spinner uses role="progressbar" with the configured numeric range and bounded value.
  • A visible label is connected with aria-describedby; when label is present, the standalone aria-label is omitted to avoid duplicate announcements.
  • Use aria-label for an indicator without a visible label.
  • The decorative visual is hidden from assistive technology with aria-hidden="true".
  • Reduced-motion preferences are respected automatically.

API Reference

PropTypeDefaultDescription
aria-labelstringLoadingAccessible name when no visible label is provided.
classNamestring—Additional class name on the root element.
labelReactNode—Visible loading or progress message.
labelPositioninline | belowinlinePlaces the label beside or below the visual.
variantring | dots | bars | pulseringAnimated visual treatment.
sizexs | sm | default | lg | xldefaultIndicator size.
toneprimary | secondary | success | danger | light | inheritprimaryTheme color applied through currentColor.
valuenumber—Enables determinate progress and progressbar semantics.
minnumber0Inclusive lower bound for determinate progress.
maxnumber100Inclusive upper bound for determinate progress.
trackbooleantrueShows the inactive ring track; relevant to ring.
speednumber1Animation duration in seconds; minimum effective value is 0.1.
thicknessnumber3Ring border width in pixels; minimum effective value is 1.
overlaybooleanfalsePositions Spinner over the nearest positioned container.
fullscreenbooleanfalsePositions Spinner as a fixed viewport overlay.
  • Progress — use when a linear or circular progress track is the primary visualization.
  • Button — use its loading state when an action button is waiting.
  • Badge — badges can show a compact loading state for async content.

For live examples of every Spinner mode, see the Spinner component showcase.