apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Spinner
Variant↳ other

Feedback

Spinner

Standalone loading indicator primitive. Three pure-CSS variants (ring / dots / pulse), five sizes plus numeric escape hatch, seven palette role colors plus currentColor fallback, prefers-reduced-motion aware. No JS render loops, no Motion library.

Spinner

The standalone loading indicator. Drop <Spinner /> anywhere — beside text, inside a button, as an EmptyState graphic — and it does the right thing with a few hundred bytes of CSS-driven motion. No JS render loops. No Motion library. No useEffect.

Overview — sizes, colors, and variants

Loading preview…
Overview.tsx

Anatomy

tsx
●        ← <Spinner variant="ring" />   (default, SVG arc rotating)
  ● ● ●     ← <Spinner variant="dots" />   (three staggered scaling dots)
   ◉        ← <Spinner variant="pulse" />  (sonar disc, scale + fade)
●        ← <Spinner variant="ring" />   (default, SVG arc rotating)
  ● ● ●     ← <Spinner variant="dots" />   (three staggered scaling dots)
   ◉        ← <Spinner variant="pulse" />  (sonar disc, scale + fade)

Variants

VariantGlyphWhen to reach for it
ringSVG circle, partial arc rotating around a faint trackDefault. Universal loading icon.
dotsThree dots scaling + fading on staggered delays"Thinking" feel — playful, conversational surfaces.
pulseSingle disc expanding sonar-styleSubtle — pairs well with cards / empty states.

All three are pure CSS. The ring uses Tailwind's built-in animate-spin; the other two use two custom keyframes registered in the DS Tailwind preset (spinner-bounce + spinner-pulse), so any consumer of the preset gets the animations for free without an extra globals.css import.

Sizes

TokenDiameterCommon use
xs12 pxInside small badges / chips
sm16 pxInline with body text, inside small buttons
md20 pxDefault. Standalone next to a label.
lg32 pxCard-level loading state
xl48 pxSection-level loading state
number— pxEscape hatch: <Spinner size={120} /> for graphics.

Speeds

SpeedDurationNotes
slow1200 ms"Deliberate, still working."
normal800 msDefault. Matches Tailwind's animate-spin.
fast500 ms"Almost done."

Routed via inline animation-duration, so the keyframe utility class stays stable across every speed.

Examples

Default — a single ring spinner

Loading preview…
Basic.tsx

ring / dots / pulse

Loading preview…
Variants.tsx

Five size tokens plus numeric escape hatch

Loading preview…
Sizes.tsx

Every palette role

Loading preview…
Colors.tsx

slow / normal / fast

Loading preview…
Speeds.tsx

Ring stroke width — 1 / 2 / 3

Loading preview…
Thickness.tsx

Ring background track opacity

Loading preview…
TrackOpacity.tsx

Label placement — hidden / end / bottom

Loading preview…
WithLabel.tsx

Inline with text — currentColor fallback

Loading preview…
Inline.tsx

Sized large for an EmptyState graphic

Loading preview…
EmptyState.tsx

Full matrix — every variant × every size

Loading preview…
AllVariants.tsx

Props

PropTypeDefaultNotes
variant'ring' | 'dots' | 'pulse''ring'Visual style. Responsive via { base, md } shape.
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | number'md'Diameter. Numeric = px.
color'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral'—Palette role. Omit to inherit currentColor.
thickness1 | 2 | 32Ring stroke width. ring variant only.
speed'slow' | 'normal' | 'fast''normal'Mapped to 1200 / 800 / 500 ms inline.
trackOpacitynumber (0..1)0.2Ring background track opacity. ring variant only.
labelstring'Loading'Accessible label. Visible when labelPlacement !== 'hidden'.
labelPlacement'hidden' | 'end' | 'bottom''hidden'Where the label renders. hidden is sr-only.
classNamestring—Merged via tailwind-merge. Last-wins.
sxSx—Theme-aware inline style.

Accessibility

  • Wrapper announces. <Spinner /> carries role="status" + aria-busy="true" + aria-live="polite". Screen readers announce the loading state without taking focus.
  • Label semantics adapt to placement. When labelPlacement="hidden" the label is applied as aria-label on the wrapper and no inner span is rendered — avoids double-announcement. When labelPlacement="end" | "bottom" the visible label text becomes the announcement (no wrapper aria-label).
  • The animated glyph is aria-hidden. SVG, dot row, pulse disc — all hidden from AT. Only the wrapper (or its visible label child) is in the accessibility tree.
  • prefers-reduced-motion. All three variants halt under reduced motion via the Tailwind motion-reduce: variant. The label still announces. The glyph stays visible as a static representation.
  • axe-core. Zero violations across the variant × color × labelPlacement matrix.
  • currentColor fallback. Drop <Spinner /> (no color prop) inside a <Button color="primary"> or an <a> and it inherits the surrounding text color automatically. The single most common pattern needs no role override.

Theming

ts
defineTheme({
  components: {
    Spinner: {
      defaultProps: { variant: 'dots', speed: 'slow' },
      styleOverrides: {
        root: 'gap-3', // wider visible gap when a label is rendered
        label: 'font-medium',
      },
    },
  },
});
defineTheme({
  components: {
    Spinner: {
      defaultProps: { variant: 'dots', speed: 'slow' },
      styleOverrides: {
        root: 'gap-3', // wider visible gap when a label is rendered
        label: 'font-medium',
      },
    },
  },
});

Props

PropTypeDefaultDescription
colorResponsiveValue<SpinnerColor>—Palette role driving the spinner glyph color. Omit to inherit `currentColor` from the surrounding text — the right move when nesting inside `<Button>`, `<a>`, or any colored surface that already sets `color`.
labelstring—Accessible label. Defaults to `"Loading"` so the spinner is always announced. Override to disambiguate (`"Loading users"`, `"Fetching invoices"`, …) or to localize.
labelPlacementenum'hidden'Where (or whether) to render the visible label text.
sizeSpinnerSize'md'Diameter (token or px).
speedenum'normal'Animation duration family.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
thicknessenum2Ring stroke width in px. `ring` variant only.
trackOpacitynumber0.2Opacity of the ring's background track (0..1). `ring` variant only. Lower it to 0 for a trackless ring.
variantResponsiveValue<SpinnerVariant>'ring'Visual style.