Free shadcn/ui spinner component for React — a Loader2 + animate-spin loading indicator. Size it, color it, drop it in a button, or center it as a loading state, plus bonus ring/dots/bars CSS styles.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

lucide-react
utils

Props

No props documented yet.

Copied!
components/ui/spinner.tsx
import { Loader2Icon } from "lucide-react"

import { cn } from "@/lib/utils"

function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
  return (
    <Loader2Icon
      role="status"
      aria-label="Loading"
      className={cn("size-4 animate-spin", className)}
      {...props}
    />
  )
}

export { Spinner }

Examples

Sizes

The spinner at four sizes — controlled with a single `size-*` utility.

Packages

spinner

Props

No props documented yet.

Copied!
components/ui/spinner-demo-01.tsx
import { Spinner } from "@/components/ui/spinner"

export default function SpinnerDemo01() {
  return (
    <div className="flex items-center gap-6">
      <Spinner className="size-3" />
      <Spinner className="size-4" />
      <Spinner className="size-6" />
      <Spinner className="size-8" />
    </div>
  )
}

Loading button

The #1 use — a Spinner inside a disabled Button for a pending action.

Packages

spinner
button

Props

No props documented yet.

Copied!
components/ui/spinner-button-01.tsx
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"

export default function SpinnerButton01() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button disabled>
        <Spinner />
        Please wait
      </Button>
      <Button variant="outline" disabled>
        <Spinner />
        Saving
      </Button>
      <Button size="icon" disabled>
        <Spinner />
        <span className="sr-only">Loading</span>
      </Button>
    </div>
  )
}

Colors

The spinner uses `currentColor` — tint it with any `text-*` utility.

Packages

spinner

Props

No props documented yet.

Copied!
components/ui/spinner-colors-01.tsx
import { Spinner } from "@/components/ui/spinner"

export default function SpinnerColors01() {
  return (
    <div className="flex items-center gap-6">
      <Spinner className="size-6" />
      <Spinner className="size-6 text-primary" />
      <Spinner className="size-6 text-destructive" />
      <Spinner className="size-6 text-muted-foreground" />
      <Spinner className="size-6 text-green-600" />
    </div>
  )
}

Loading state

A centered spinner with a label — a section or card loading state.

Packages

spinner

Props

No props documented yet.

Copied!
components/ui/spinner-card-01.tsx
import { Spinner } from "@/components/ui/spinner"

export default function SpinnerCard01() {
  return (
    <div className="flex h-48 w-full max-w-sm items-center justify-center rounded-lg border">
      <div className="flex flex-col items-center gap-3">
        <Spinner className="size-8 text-muted-foreground" />
        <p className="text-muted-foreground text-sm">Loading…</p>
      </div>
    </div>
  )
}

Inline with text

A spinner inline with a status message — "Checking…" / "Uploading…".

Packages

spinner

Props

No props documented yet.

Copied!
components/ui/spinner-inline-01.tsx
import { Spinner } from "@/components/ui/spinner"

export default function SpinnerInline01() {
  return (
    <div className="flex flex-col gap-3 text-sm">
      <span className="flex items-center gap-2">
        <Spinner className="size-4" />
        Checking availability…
      </span>
      <span className="text-muted-foreground flex items-center gap-2">
        <Spinner className="size-4" />
        Uploading files…
      </span>
    </div>
  )
}

Spinner styles (ring / dots / bars)

Bonus spinner styles built with plain CSS — beyond the canonical Loader2.

Packages

spinner

Props

No props documented yet.

Copied!
components/ui/spinner-styles-01.tsx
// Bonus spinner styles built with plain CSS — beyond the canonical Loader2 Spinner.
// The "bars" variant uses the .animate-spinner-bar keyframe from globals.css.
export default function SpinnerStyles01() {
  return (
    <div className="flex items-center gap-10">
      <div className="flex flex-col items-center gap-2">
        <div className="size-8 animate-spin rounded-full border-2 border-current border-t-transparent text-primary" />
        <span className="text-muted-foreground text-xs">Ring</span>
      </div>
      <div className="flex flex-col items-center gap-2">
        <div className="flex items-center gap-1">
          <div className="size-2 animate-bounce rounded-full bg-primary [animation-delay:-0.3s]" />
          <div className="size-2 animate-bounce rounded-full bg-primary [animation-delay:-0.15s]" />
          <div className="size-2 animate-bounce rounded-full bg-primary" />
        </div>
        <span className="text-muted-foreground text-xs">Dots</span>
      </div>
      <div className="flex flex-col items-center gap-2">
        <div className="flex h-8 items-end gap-1">
          <div className="animate-spinner-bar h-full w-1.5 origin-bottom rounded-full bg-primary [animation-delay:-0.4s]" />
          <div className="animate-spinner-bar h-full w-1.5 origin-bottom rounded-full bg-primary [animation-delay:-0.2s]" />
          <div className="animate-spinner-bar h-full w-1.5 origin-bottom rounded-full bg-primary" />
        </div>
        <span className="text-muted-foreground text-xs">Bars</span>
      </div>
    </div>
  )
}

Frequently asked questions

It is a loading indicator — Lucide's Loader2 icon with the animate-spin class, wrapped with role="status" and an aria-label for accessibility. Because it is an SVG icon, you size it with size-* and color it with text-*. There is no extra dependency beyond lucide-react.
Pass a size-* utility in the className: . The default is size-4 (16px). Use size-3 for inline text, size-8 or larger for a page or section loading state. It scales cleanly because it is a vector icon.
The spinner draws with currentColor, so set a text color: or text-destructive, text-muted-foreground, or any custom text-* value. Inside a Button it inherits the button's text color automatically.
Put the Spinner before the label and disable the button while loading: . The Button already sizes and spaces SVG children, so it lines up without extra styling. The Loading button example shows default, outline, and icon variants.
Wrap it in a flex container that fills the area: a div with flex items-center justify-center and a height, with the Spinner (e.g. size-8 text-muted-foreground) and an optional label inside. The Loading state example is ready to drop into a Suspense fallback or a "while fetching" branch.
Yes. The canonical shadcn Spinner is the Loader2 icon, but the Spinner styles example also includes a ring (a bordered circle with animate-spin), bouncing dots (animate-bounce with staggered delays), and scaling bars (a small keyframe) — all built with plain CSS and your tokens, no library.
The Spinner sets role="status" and aria-label="Loading" so assistive tech announces that something is loading. When the spinner sits inside a button or label that already describes the action, that context is usually enough; for a standalone page loader, keep the role so the loading state is announced.