UnflowUI
Components

Spinner

An animated circular progress indicator. Use it standalone or compose it inside Button and IconButton for loading states.

Overview

Spinner is a pure SVG component — no external dependencies. The size prop is a pixel number that controls width and height. The stroke color inherits via stroke-current so Spinner picks up whatever color or text-* class is on a parent element.

Button and IconButton internally use Spinner for their loading state via setupButtonSpinner, which automatically scales size and thickness for the button's current size prop.

Import

import { Spinner } from '@unflow/ui/components/Spinner';

Props

PropTypeDefaultDescription
sizenumber32Diameter in pixels
thicknessnumber2Stroke width in pixels
speed'slow' | 'normal' | 'fast''normal'Animation speed

All standard HTMLDivElement props are forwarded to the wrapper <div role="status">.

Default

<Spinner aria-label="Loading" />

Sizes

16px
24px
32px
48px
64px
<Spinner size={16} aria-label="Loading" />
<Spinner size={24} aria-label="Loading" />
<Spinner size={32} aria-label="Loading" />
<Spinner size={48} aria-label="Loading" />
<Spinner size={64} aria-label="Loading" />

Speeds

slow
normal
fast
<Spinner speed="slow" aria-label="Loading slowly" />
<Spinner speed="normal" aria-label="Loading" />
<Spinner speed="fast" aria-label="Loading quickly" />
SpeedDuration
slow2.75s
normal1.5s
fast0.75s

Color

Spinner inherits its stroke color from the CSS color property via stroke-current. Override by setting a text-* class on the Spinner or a parent:

<Spinner className="text-indigo-500" aria-label="Loading" />
<Spinner className="text-red-500" aria-label="Loading" />

{/* or inherit from parent */}
<div className="text-brand-500">
  <Spinner aria-label="Loading" />
</div>

Inside Button

<Button label="Save changes" loading />

Button handles Spinner sizing automatically based on its size prop. Use slotProps.loading.spinner to override:

<Button
  label="Save"
  loading
  slotProps={{ loading: { spinner: { size: 14, thickness: 1.5 } } }}
/>

Accessibility

  • Renders <div role="status"> — screen readers announce it as a live region.
  • Always provide aria-label so the announcement is meaningful: aria-label="Loading search results" not just "Loading".
  • Remove from the DOM (or use aria-hidden) when loading is complete.