Skip to content

Now in the official shadcn registry directory.

Start free

Spinner

A loading spinner with 3 tones × 4 sizes, announced to screen readers via role=status and a configurable label.

displayfeedback
A free styled version is available

Installation

Run the following command

npx shadcn@latest add @ai2/spinner

Dependencies, the @ai2/tokens theme and the component file are installed together.

Usage

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

<Spinner tone="brand" size="lg" />

Both axes are optional - the default is a medium spinner that inherits the current text color.

Examples

Tones

tone="current" inherits the text color, so the spinner matches whatever it sits inside - for example a Button's label color.

Sizes

With text

Deploying to production…

The default tone follows the wrapper's text color, and the label prop replaces the default "Loading" announcement.

Props

Spinner also accepts every native <svg> prop.

PropTypeDefaultDescription
tone"current" | "brand" | "muted""current"Color of the spinner. current inherits the surrounding text color, so it matches whatever context it sits in.
size"sm" | "md" | "lg" | "xl""md"Diameter of the spinner, from size-4 to size-8.
labelstring"Loading"Accessible name announced by screen readers via aria-label.

ai2 Spinner: a loading indicator for React with tones and sizes

The ai2 Spinner is a shadcn-compatible spinner component for React, styled with Tailwind CSS v4. It renders a rotating loader icon for indeterminate waits: pending buttons, background fetches, page transitions and anywhere a skeleton has no layout to mirror. It comes with 3 tones and 4 sizes from a single cva, and it announces itself to screen readers out of the box.

It ships through the shadcn registry format, so one CLI command, an MCP agent, or a copy-paste puts the source in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.

What is the ai2 Spinner?

It is a single part: Spinner, the lucide Loader2 SVG spun with Tailwind's animate-spin utility. There is no radix primitive and no JavaScript animation. It carries role="status" and an aria-label that defaults to "Loading" and is configurable via the label prop.

The cva defines two axes: tone (current, brand, muted) and size (sm, md, lg, xl, from size-4 to size-8). The default tone is current, which inherits the surrounding text color, so a spinner dropped inside a button, an alert or a colored wrapper matches its context with zero configuration.

Why use it

  • Honest accessibility built in: The SVG ships with role="status" and an aria-label, so screen readers announce the loading state without any wrapper markup. The label prop swaps the announcement for context-specific text like "Deploying".
  • Context-matching color: tone="current" inherits the text color of whatever contains the spinner, so one component works inside buttons, badges, alerts and plain text without per-case overrides.
  • Four sizes for real layouts: sm (size-4) fits inline text and buttons, md is the default, lg suits cards and xl suits page-level loading states.
  • Zero-dependency animation: The rotation is Tailwind's animate-spin utility, pure CSS on a lucide icon that shadcn projects already have. No animation library is added.
  • Agent-readable metadata: The registry item states the tone and size axes in plain words, so an MCP agent can find, inspect and install it without guessing.

Features

  • shadcn registry install: One command adds the component, its lucide-react dependency and the @ai2/tokens theme to your project.
  • 3 tones from the theme: current inherits the surrounding text color, brand uses the brand token, muted uses the muted-foreground token for quiet background activity.
  • 4 sizes: sm, md, lg and xl map to size-4 through size-8, covering inline, control, card and page-level loading states.
  • Configurable announcement: The label prop sets the aria-label, so screen readers can hear "Deploying" or "Saving" instead of a generic "Loading".
  • Exported variants: spinnerVariants is exported alongside the component, so you can apply the exact same tone and size classes to a custom SVG if you swap the icon.
  • TypeScript source: The file you install is typed end to end and forwards every native svg prop, so autocomplete covers the full API.

Production tips

  • Pair it with visible text when possible: A spinner next to a short status line like "Deploying to production" informs sighted users and screen reader users alike. Keep the label prop in sync with the visible text.
  • Prefer skeletons for known layouts: If you know the shape of the incoming content, a skeleton communicates more. Reserve the spinner for indeterminate operations like submits and background work.
  • Match size to the container: Use sm inside buttons and table rows so the line height does not jump, and xl only for full-page or full-panel waits.
  • Let tone="current" do the work: Instead of forcing a color, place the spinner inside a wrapper that already has the right text color, for example text-success while a deploy is healthy.
  • Avoid spinner farms: Several spinners pulsing in one view read as chaos. Lift the loading state to the highest reasonable container and show one indicator.

Works with the rest of ai2

The spinner composes naturally with the rest of the registry. Show pending actions inside an ai2 Button, indicate background activity next to an ai2 Badge in a table row, or hold a panel open while a ai2 Dialog form submits.

For content with a known shape, reach for the ai2 Skeleton instead, and when you can report completion percentage, use ai2 Progress. Everything shares one token source, so loading states stay visually consistent in both light and dark mode.

FAQs

Get new components in your inbox

Component drops, new themes and release notes. No spam, and you can unsubscribe from any email.

© 2026 ai2 design system · MIT licensed · Built with its own components