Skip to main content
Markdown

Spinner

A compact indeterminate loading indicator with built-in status semantics.

Usage

import { Spinner } from '@takeoff-ui/react-spar';
<Spinner aria-label="Loading results" />

Playground


function PlaygroundDemo() {
  return (
    <div className="flex items-center justify-center gap-4">
      <Spinner />
      <Spinner variant="primary" />
      <Spinner variant="success" size="large" aria-label="Saving" />
    </div>
  );
}

render(<PlaygroundDemo />);

Variants


function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-5">
      <Spinner variant="primary" aria-label="Primary loading" />
      <Spinner variant="secondary" aria-label="Secondary loading" />
      <Spinner variant="neutral" aria-label="Neutral loading" />
      <Spinner variant="info" aria-label="Info loading" />
      <Spinner variant="success" aria-label="Success loading" />
      <Spinner variant="danger" aria-label="Danger loading" />
      <Spinner variant="warning" aria-label="Warning loading" />
    </div>
  );
}

render(<VariantsDemo />);

Appearances


function AppearancesDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-5">
      <Spinner appearance="rounded" aria-label="Rounded loading" />
      <Spinner appearance="dots" aria-label="Dots loading" />
      <Spinner appearance="lines" aria-label="Lines loading" />
      <Spinner appearance="pulse" aria-label="Pulse loading" />
      <Spinner appearance="threeDots" aria-label="Three dots loading" />
      <Spinner appearance="loader" aria-label="Loader loading" />
      <Spinner appearance="logo" aria-label="Logo loading" />
    </div>
  );
}

render(<AppearancesDemo />);

Sizes


function SizesDemo() {
  return (
    <div className="flex items-center justify-center gap-5">
      <Spinner size="small" aria-label="Small loading" />
      <Spinner size="base" aria-label="Base loading" />
      <Spinner size="large" aria-label="Large loading" />
      <Spinner size="xlarge" aria-label="Extra large loading" />
    </div>
  );
}

render(<SizesDemo />);

Label

Spinner does not provide label layout. When visible loading text is needed, compose it with Label and connect the text with aria-labelledby.


function LabelDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <div className="flex flex-col items-center gap-1">
        <Spinner aria-labelledby="spinner-flight-label" variant="primary" />
        <Label as="span" id="spinner-flight-label">Loading flights</Label>
      </div>
      <div className="flex flex-col items-center gap-1">
        <Spinner appearance="threeDots" aria-labelledby="spinner-saving-label" variant="success" />
        <Label as="span" id="spinner-saving-label">Saving</Label>
      </div>
      <div className="flex flex-col items-center gap-1">
        <Spinner appearance="logo" aria-labelledby="spinner-preparing-label" variant="neutral" />
        <Label as="span" id="spinner-preparing-label">Preparing</Label>
      </div>
    </div>
  );
}

render(<LabelDemo />);

Accessibility

Spinner renders a polite status by default. Use aria-label or aria-labelledby for a domain-specific name; use native aria-hidden only when the spinner is purely decorative next to another loading message. When aria-hidden is true, Spinner does not apply the status role or default accessible name.


function AccessibilityDemo() {
  return (
    <div className="flex items-center justify-center gap-5">
      <Spinner aria-label="Loading flight results" />
      <Spinner aria-hidden />
    </div>
  );
}

render(<AccessibilityDemo />);

API Reference

Spinner

Props

NameTypeDefaultDescription
sizeSpinnerSize'base'Size scale.
appearanceSpinnerAppearance'rounded'Visual spinner style.
variantSpinnerVariant'neutral'Color variant.
classNamesPartial<Record<SpinnerSlot, string>>-Per-slot extra classes.
slotPropsPartial<Record<SpinnerSlot, React.HTMLAttributes<HTMLElement>>>-Per-slot HTML-attribute overrides.
classNamestring-Appends custom classes to the root slot.
aria-labelstring'Loading'Accessible name for the loading status. Override it for domain-specific loading text.
aria-labelledbystring-ID reference for visible loading text. When provided, the default aria-label is not applied.
aria-hiddenbooleanfalseHides a decorative spinner from assistive technology. When true, status role and default accessible name are not applied.

Data attributes

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-variantAlwaysReflects the resolved variant prop for theme recipe scoping.
data-sizeAlwaysReflects the resolved size prop for theme recipe scoping.
data-typeAlwaysReflects the resolved appearance prop for theme recipe scoping.

Type Definitions

NameDefinition
SpinnerSize'small' | 'base' | 'large' | 'xlarge'
SpinnerAppearance'rounded' | 'dots' | 'lines' | 'pulse' | 'threeDots' | 'loader' | 'logo'
SpinnerVariant'primary' | 'secondary' | 'neutral' | 'info' | 'success' | 'danger' | 'warning'
SpinnerSlot'root' | 'indicator'