Skip to content
Components

Spinner

A turning arc that shows work is running when the amount is unknown: loading a list, checking a value. Its name is read by screen readers and never shown.

Preview

Loading

Props

  • size
  • tone
import { Spinner } from "@/components/ui/spinner"

<Spinner label="Loading" />

Usage

When to use

  • Show that work runs when its length is unknown: loading comments, checking a value.
  • A small region or a control that waits, right where the result will appear.

When not to use

  • Work whose amount is known, such as an upload. Use Progress.
  • A page or list whose layout you know while it loads. Use Skeleton.

Variants

size

sm (16px) inside sm and md controls; md (20px) inside lg controls; lg (24px) on its own in a region that loads.

  • Loading

    sm

  • Loading

    mddefault

  • Loading

    lg

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

<Spinner size="sm" label="Loading" />
<Spinner label="Loading" />
<Spinner size="lg" label="Loading" />

tone

secondary on surfaces; accent when the spinner is the main thing on screen; current takes the text color around it, such as a label inside a Button.

  • Loading

    secondarydefault

  • Loading

    accent

  • Loading

    current

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

<Spinner label="Loading" />
<Spinner tone="accent" label="Loading" />
<Spinner tone="current" label="Loading" />

Examples

Loading a list

Loading comments
import { Spinner } from "@/components/ui/spinner"

<div className="flex items-center gap-sm text-sm text-text-secondary">
  <Spinner size="sm" label="Loading comments" />
  <span aria-hidden="true">Loading comments</span>
</div>

On its own

lg and accent when it is the only thing in the region.

Loading dashboard
import { Spinner } from "@/components/ui/spinner"

<Spinner size="lg" tone="accent" label="Loading dashboard" />

Do and don't

Do

  • Name what loads in the label: Loading comments.
  • Put it where the content will appear, so the eye is already there when it does.
  • Use tone current inside a control, so it takes the control's text color.

Don't

  • Don't use it when the amount of work is known, such as an upload. Use Progress.
  • Don't use it for a whole page or list whose layout you know. Use Skeleton.
  • Don't show several spinners for one task.

Props

NameTypeValuesDefaultDescription
sizeenumsm | md | lgmdsm (16px) inside sm and md controls; md (20px) inside lg controls; lg (24px) on its own in a region that loads.
toneenumsecondary | accent | currentsecondarysecondary on surfaces; accent when the spinner is the main thing on screen; current takes the text color around it, such as a label inside a Button.
labelstring-LoadingWhat is loading, for screen readers: Loading comments. Never shown.

Anatomy

PartDescription
rootAn inline box the size of the icon, in an icon color.
iconTabler Loader2: 16px (sm), 20px (md) or 24px (lg). It turns under motion-safe.
labelVisually hidden text that names what is loading.

Accessibility

Role: status

  • The root is a status region, and label (Loading by default) is its visually hidden text. Name what loads: Loading comments.
  • The arc is decorative (aria-hidden); the label carries the meaning.
  • It turns only when the user allows motion. Under prefers-reduced-motion it holds still, and the label still says what is happening.
  • Remove it when the work is done, and move focus to the result only if the user asked for it.

Install

npx shadcn add https://werewolf-ui.vercel.app/r/spinner.json

This also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").