# Shadcn Spinner

> 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.

Source: https://designrevision.com/components/spinner

The **Shadcn Spinner** is the simplest loading indicator: Lucide's `Loader2` icon with `animate-spin` and the right accessibility attributes. Because it is just an SVG, you **size it with `size-*`** and **color it with `text-*`** — and it slots straight into a button or a centered loading state. The examples below cover sizing, color, loading buttons, overlays, and a few bonus spinner styles.

## It's a Loader2 icon

The whole component is a wrapper:

```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}
    />
  )
}
```

The only dependency is `lucide-react`. Swap `Loader2Icon` for `LoaderIcon` or `LoaderCircleIcon` if you prefer a different look.

## Size and color

- **Size** with `size-*`: `<Spinner className="size-3" />` for inline, `size-8` for a page loader. Default is `size-4`.
- **Color** with `text-*`: the icon uses `currentColor`, so `<Spinner className="text-primary" />` (or `text-destructive`, `text-muted-foreground`) just works — and inside a button it inherits the button's color.

## The loading button

The most common use is a pending button — put the spinner before the label and disable it:

```tsx
<Button disabled>
  <Spinner />
  Please wait
</Button>
```

The Button already spaces and sizes SVG children, so no extra styling is needed.

## A centered loading state

For a section or card that is still fetching, center the spinner:

```tsx
<div className="flex h-48 items-center justify-center">
  <Spinner className="size-8 text-muted-foreground" />
</div>
```

This is also the ideal **`Suspense` fallback** or "while loading" branch.

## Bonus: ring, dots & bars

The canonical spinner is the Loader2 icon, but the **Spinner styles** example shows three more, all in plain CSS with your tokens:

- **Ring** — a bordered circle with one transparent edge and `animate-spin`.
- **Dots** — three dots with `animate-bounce` and staggered `animation-delay`.
- **Bars** — scaling bars driven by a small keyframe.

## Accessibility

The Spinner carries `role="status"` and `aria-label="Loading"`, so screen readers announce the loading state. Keep that for standalone loaders; when the spinner is inside a button or label that already names the action, the surrounding context describes it.

## Installation

### spinner

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner
```

**Dependencies:** lucide-react, utils

**Usage & accessibility:** A loading spinner — Lucide's Loader2 icon with animate-spin, plus role="status" and aria-label="Loading" for accessibility. It is an <svg>, so size it with size-* (default size-4) and color it with text-* (it uses currentColor). Drop it inside a Button for a loading button, or center it for a page/section loading state.

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-demo-01
```

**Dependencies:** spinner

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-button-01
```

**Dependencies:** spinner, button

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-colors-01
```

**Dependencies:** spinner

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-card-01
```

**Dependencies:** spinner

```tsx
// 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…".

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-inline-01
```

**Dependencies:** spinner

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/spinner-styles-01
```

**Dependencies:** spinner

```tsx
// 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>
  )
}
```
