1
ComponentsFeedback

Spinner

Indeterminate loading status for saving, refreshing and bounded asynchronous work.

Purpose

Use Spinner for an indeterminate operation whose completion percentage is unknown, such as saving, refreshing or loading a bounded panel.

Indeterminate loading

import { Spinner } from "@/components/ui/spinner";

export function Example() {
  return <Spinner aria-label="Loading" />;
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/spinner

Import

import { Spinner } from "@/components/ui/spinner";
import { Button } from "@/components/ui/button";

Need the machine-readable source metadata? View registry JSON.

Usage

Spinner indicates an indeterminate wait. Prefer Button loading states for button-specific actions.

import { Spinner } from "@/components/ui/spinner";

export function Example() {
  return <Spinner aria-label="Loading" />;
}

Examples

Variants, sizing via className, color inheritance and loading compositions all build on the same primitive.

Variants

The default stays the animated Loader2 icon; variant switches to ring, dots, bars, spokes, pulse or comet.

comet
ring
dots
bars
spokes
pulse
{(["comet", "ring", "dots", "bars", "spokes", "pulse"] as const).map((variant) => (
  <Spinner aria-label={`${variant} loading`} key={variant} variant={variant} />
))}

Sizes

Size comes from className — there is no size prop.

<Spinner aria-label="Small loading" className="size-4" />
<Spinner aria-label="Medium loading" className="size-6" />
<Spinner aria-label="Large loading" className="size-8" />

Muted color

Spinner inherits currentColor, so text color utilities recolor it.

<Spinner aria-label="Muted loading" className="text-muted-foreground" />

With label

When visible text carries the status, hide the spinner from assistive tech.

Loading…
<span className="flex items-center gap-2 text-muted-foreground text-sm">
  <Spinner aria-hidden className="size-4" />
  Loading…
</span>

In button

Button has its own loading prop; a manual leading Spinner is the alternative.

<Button loading type="button">
  Saving
</Button>

<Button type="button" variant="outline">
  <Spinner aria-hidden className="size-4" />
  Syncing
</Button>

Card loading

Loading workspace…
<div className="grid place-items-center gap-2 rounded-lg border p-6">
  <Spinner aria-hidden className="size-6 text-muted-foreground" />
  <span className="text-muted-foreground text-xs">Loading workspace…</span>
</div>

Overlay

A translucent backdrop keeps existing content visible while new data loads.

<div className="relative grid min-h-24 place-items-center overflow-hidden rounded-lg border">
  <div className="grid gap-2">
    <div className="h-2 w-40 rounded bg-muted" />
    <div className="h-2 w-28 rounded bg-muted" />
  </div>
  <div className="absolute inset-0 grid place-items-center bg-background/64 backdrop-blur-sm">
    <Spinner aria-label="Content loading" className="size-6" />
  </div>
</div>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Spinner accepts standard element props and variant="default" | "ring" | "dots" | "bars" | "spokes" | "pulse" | "comet".
  • Size and color inherit from the surrounding context unless an existing utility explicitly changes them.
  • Button loading should use the Button loading contract rather than manually placing a separate Spinner next to the label.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Each variant owns its loading animation; do not stack another spin, pulse or duration class on top.
  • Both the provider override and the operating-system reduced-motion preference stop root and child animations through the shared siteplane-spinner-motion contract. The static status indicator and its semantics remain visible.

Accessibility

  • A standalone Spinner uses role="status" and needs a context-specific accessible label when Loading is not descriptive enough.
  • Keep nearby visible loading text available to assistive technology.
  • Use Progress when a real percentage or stage is known.

Implementation Guidance

  • Prefer the default variant unless another documented form better matches the available space.
  • Do not invent a new loader animation in product code.
  • Avoid showing several independent spinners for one operation.

On This Page