Skip to content

Spinner

FeedbackSince 0.2.0
import { Spinner } from '@absoluteui/components/spinner';
import '@absoluteui/components/spinner/style.css';

Using Angular? See Spinner for Angular.

An indeterminate loading indicator. The host is a role="status" region (implicitly aria-live="polite") containing a decorative rotating icon and a localized label. It provides:

  • a label that is announced politely when the spinner appears;
  • an optional delay, so fast operations never flash a spinner;
  • a static icon under reduced motion.
  • Short waits of unknown duration (loading a panel, saving).
  • Inside a busy region (aria-busy) as the announced status next to Skeleton placeholders.
  • Measurable progress: use ProgressBar.
  • Blocking the whole page: prefer skeletons or inline progress.
  • Inside buttons as the only feedback without a label.
import { Spinner } from '@absoluteui/components/spinner';
const host = document.querySelector<HTMLElement>('#results-status');
if (host) {
const spinner = Spinner.create(host, { label: 'Loading results', delay: 300 });
void fetch('/api/results').finally(() => {
spinner.destroy();
});
}
import { Spinner } from '@absoluteui/components/spinner';
const host = document.querySelector<HTMLElement>('#saving');
if (host) {
Spinner.create(host, { label: 'Saving changes', showLabel: true, size: 'sm' });
}
import { Spinner } from '@absoluteui/components/spinner';
import '@absoluteui/components/spinner/style.css';
const host = document.querySelector<HTMLElement>('#saving');
if (host) {
const spinner = Spinner.create(host, { label: 'Saving changes', showLabel: true, delay: 200 });
void fetch('/api/save', { method: 'POST' }).finally(() => {
spinner.destroy();
});
}
import { Spinner } from '@absoluteui/components/spinner';
const host = document.createElement('div');
document.body.append(host);
const spinner = Spinner.create(host, { label: 'Loading results', delay: 300 });
spinner.destroy();
<div class="aui-spinner aui-spinner--{size} [aui-spinner--pending]" role="status">
<svg class="aui-icon aui-spinner__icon" aria-hidden="true"></svg> (after the delay)
<span class="aui-spinner__label [aui-spinner__label--hidden]">Loading</span>
</div>
Spinner.create(host: HTMLElement, options?: SpinnerOptions): Spinner

Global configuration key: spinner (see Configuration).

Option Type Default Description
label string Announced (and optionally visible) label. Default: localized “Loading”.
showLabel boolean false Show the label visibly; otherwise it is visually hidden but announced.
icons Partial<{ spinner: IconRef }> Icon slot overrides.
size 'sm' | 'md' | 'lg' "md" Icon size. Globally configurable.
delay number 0 Milliseconds (integer 0..60000) before the content is shown. Globally configurable. Set at creation only.
locale string "en-US" Message locale for the default label. Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement The role=“status” host.
visible boolean Whether the delay elapsed and content is rendered.
Method Description
update(options: Partial<SpinnerOptions>): void Merge and apply options (delay cannot change).
destroy(): void Cancel a pending delay; remove rendering, role, and subscriptions.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-spinner Host (role=“status”).
aui-spinner--sm | --md | --lg Size modifiers.
aui-spinner--pending Delay not yet elapsed.
aui-spinner__icon Decorative rotating icon (aria-hidden).
aui-spinner__label Label text.
aui-spinner__label--hidden Visually hidden label.
Slot Default icon
spinner spinner

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-color-primary | --aui-color-text | --aui-font-family-sans | --aui-font-size-sm | --aui-icon-size-lg | --aui-icon-size-md | --aui-icon-size-sm | --aui-line-height-tight | --aui-space-2

type SpinnerSize = 'sm' | 'md' | 'lg';
interface SpinnerIcons { spinner: IconRef }
interface SpinnerConfig { size: SpinnerSize; delay: number }
interface SpinnerOptions extends Partial<SpinnerConfig> {
label?: string;
showLabel?: boolean;
locale?: string;
icons?: Partial<SpinnerIcons>;
}

Pattern: ARIA status live region (role=“status”, implicit aria-live=“polite”).

Key Context Action
None Any Not focusable; no keyboard interaction.
Element Role
host status
icon none (aria-hidden=true)
Attribute Element Value
role host status
aria-hidden icon true

Not focusable. Focus is never moved.

Criterion Level Note
1.3.1 Info and Relationships A
2.2.2 Pause, Stop, Hide A
2.3.3 Animation from Interactions AAA
4.1.3 Status Messages AA

Automated test results: Accessibility and compliance.

  • The status region is rendered empty until the delay elapses; inserting the label then triggers a polite announcement.
  • Rotation stops under prefers-reduced-motion or animation: none; the static icon and the label remain.
  • The label is visually hidden by default and always remains in the accessibility tree.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

pending (the delay has not elapsed; the status region is empty) and visible.

From Trigger To
(created, delay: 0) create visible
(created, delay > 0) create pending
pending delay elapses visible (label inserted and announced)
pending / visible destroy() removed; a pending timer is cancelled
  • The region exists from creation and is empty while pending. Inserting the label into the existing region triggers a polite announcement.
  • With delay: 0 the region and its content appear in the same task, and some screen readers may not announce it. Use a delay, or create the spinner on an empty host that is already in the page.
  • Rotation stops under prefers-reduced-motion: reduce or animation: 'none'. The static icon and the label remain.
  • Manual screen-reader verification is pending.

Inline-level. The icon size follows size; the visible label wraps with the surrounding text.

Logical gap between the icon and the label. The rotation direction is not mirrored.

The icon uses color-primary and the label uses color-text. Forced-colors mode uses CanvasText.

configureAbsoluteUI({ components: { spinner: { size: 'sm', delay: 300 } } });

size applies to existing instances immediately; delay applies to new instances.

Instance options override global and theme defaults. delay is init-only; all other options are dynamic.

icons.spinner (default spinner).

The label uses textContent. No inline styles; the visually hidden label uses a CSS class.

No persistence, network access, or telemetry.

No DOM listeners; one timer while pending and one configuration subscription. The rotation is a compositor-friendly transform animation. Target: under 1 ms per Spinner.create in headless Chromium (not yet covered by the e2e performance suite).

One spinner per loading region. Many simultaneous spinners are an anti-pattern, because each one is a live region.

  • Destroying the spinner before the delay elapses means it never renders, which is the intended behavior for fast operations.
  • Changing the label while visible updates the region text, and screen readers announce the new label.
  • The default label follows the configured locale; register translations with registerMessages('spinner', locale, { loading }).
Code Category Cause
SPINNER_OPTIONS configuration Empty label.
CFG_INVALID_VALUE configuration Invalid size, or delay outside 0-60000 or not an integer.
CFG_INIT_ONLY configuration update({ delay }) on an existing instance.

A failed construction leaves the host untouched.

  • The announcement for delay: 0 depends on the screen reader (see Accessibility notes).
  • delay is init-only.
  • Manual screen-reader verification is pending.
  • Determinate progress.
  • HTML in the label.