Skip to content
Entangle UI v0.13.0

Spinner

A small loading indicator with three visual variants and four sizes. Always announces itself via role="status" and aria-label. Honors prefers-reduced-motion by halting animation and dimming the indicator.

Live Preview

import { Spinner } from 'entangle-ui';
<Spinner />
<Spinner variant="dots" size="lg" />
<Spinner showLabel label="Fetching results..." />

Variants

<Spinner variant="ring" /> {/* default — circular arc */}
<Spinner variant="dots" /> {/* three pulsing dots */}
<Spinner variant="pulse" /> {/* pulsing filled circle */}

Sizes

| Size | Pixel size | Use case | | ---- | ---------- | ------------------------------- | | xs | 12px | Inline next to short text | | sm | 14px | Compact button replacement | | md | 16px | Default | | lg | 20px | EmptyState or page-level loader |

<Spinner size="xs" />
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />

Named values map onto theme tokens. Any other string is used as a raw CSS color.

Colors

| Name | Maps to | | ----------- | ----------------------- | | accent | colors.accent.primary | | primary | colors.text.primary | | secondary | colors.text.secondary | | muted | colors.text.muted |

<Spinner color="accent" />
<Spinner color="muted" />
<Spinner color="#9b59b6" />

By default the label is provided via aria-label only. Set showLabel to render it visually next to the spinner.

Visible label

<Spinner showLabel label="Saving..." />

Replace the button content while an operation runs.

Inside a button

Click the button to simulate a 1.5s async operation.

<Button disabled={loading}>
{loading ? (
<>
<Spinner size="sm" color="primary" /> Saving...
</>
) : (
'Save'
)}
</Button>

For the standard “loading button” pattern, prefer <Button loading>.

When the user enables prefers-reduced-motion: reduce in their OS, animation is suppressed and the indicator is rendered at 60% opacity instead. No code changes required.

Prop Type Default Description
variant 'ring' | 'dots' | 'pulse' 'ring' Visual style.
size 'xs' | 'sm' | 'md' | 'lg' 'md' Size scale.
color 'accent' | 'primary' | 'secondary' | 'muted' | string 'accent' Named theme color or any CSS color string.
label string 'Loading...' Visible text and aria-label content.
showLabel boolean false Show the label visually next to the spinner.
decorative boolean false Render as purely decorative (role="presentation" + aria-hidden, no live region). Use when nested inside an existing live region such as StatusBar.
className string — Additional CSS class names.
style CSSProperties — Inline styles.
testId string — Test identifier for automated testing.
ref Ref<HTMLSpanElement> — Ref to the underlying span element.
  • Root element has role="status" and aria-live="polite" so screen readers announce the loading state when the spinner appears
  • aria-label defaults to "Loading..." and can be overridden via label
  • When showLabel is false, the label is rendered in a visually-hidden span so it remains discoverable by assistive tech
  • Animation is automatically halted under prefers-reduced-motion: reduce
  • Set decorative when the spinner sits inside an existing live region (e.g. a StatusBar, which is already role="status"). It then renders role="presentation" + aria-hidden with no aria-live, so the activity is announced once by the surrounding region instead of twice:
<StatusBar>
<StatusBar.Section side="left">
<Spinner decorative size="sm" />
<StatusBar.Item>Building…</StatusBar.Item>
</StatusBar.Section>
</StatusBar>