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>