# Shadcn Skeleton

> Free shadcn/ui skeleton loader for React — build any loading skeleton (card, list, table, article, dashboard, form, profile) from one primitive, with no extra dependency. Default pulse plus a shimmer variant.

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

The **Shadcn Skeleton** is the simplest component on the site and one of the most useful: a single `<div>` placeholder you compose into any **loading skeleton** — card, list, table, article, dashboard, form, or profile. There is **no dependency** (you do not need `react-loading-skeleton`), and you control every piece with Tailwind. The examples below are drop-in loading states for the layouts you build most.

## What is a skeleton screen?

A **skeleton screen** shows the *shape* of your content — boxes where images go, bars where text goes — while the real data loads. Compared with a blank page or a spinner, it:

- sets the **layout immediately**, so nothing jumps when content arrives,
- communicates *what* is loading, not just *that* something is,
- makes the wait feel shorter.

## One primitive, every layout

The component itself is trivial:

```tsx
function Skeleton({ className, ...props }) {
  return (
    <div
      className={cn("bg-accent animate-pulse rounded-md", className)}
      {...props}
    />
  )
}
```

You build everything by **sizing and shaping** that div with a `className`:

```tsx
<div className="flex items-center space-x-4">
  <Skeleton className="size-12 rounded-full" />   {/* avatar */}
  <div className="space-y-2">
    <Skeleton className="h-4 w-[250px]" />          {/* line */}
    <Skeleton className="h-4 w-[200px]" />          {/* line */}
  </div>
</div>
```

`rounded-full` for avatars, `rounded-md` for bars and blocks; `h-*`/`w-*`/`size-*` to match the real element. That is the whole technique — the nine examples here just apply it at different scales.

## Show it while loading

Render the skeleton until your data is ready, then swap in the real UI:

```tsx
{isLoading ? <CardSkeleton /> : <Card data={data} />}
```

The key to a skeleton that doesn't cause layout shift: **mirror the real layout** — the same spacing, the same number of lines, the same image aspect ratio — so the placeholder and the final content occupy the same space.

## No extra dependency

`react-loading-skeleton` is a popular package, but you don't need it here. The shadcn Skeleton is one div you already have after adding the component — zero dependencies, full Tailwind control, and it themes with your tokens. If you want that library's signature **shimmer**, use the variant below.

## Pulse vs shimmer

The default animation is **`animate-pulse`** — a gentle fade. For a **shimmer** (a highlight that sweeps across), swap the pulse for a moving gradient. This page adds a small utility:

```css
@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-shimmer {
  background-image: linear-gradient(90deg, var(--muted) 25%, var(--accent) 37%, var(--muted) 63%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
}
```

Apply `skeleton-shimmer` to a sized div instead of the pulse Skeleton — the Shimmer variant example shows it on a card.

## Skeleton vs spinner

Reach for a **skeleton** when you know the shape of what's loading (cards, lists, tables) — it reserves the layout and reads as faster. Reach for a **spinner** for short, shapeless waits (a submit, a quick toggle). For content-heavy pages, skeleton screens are the better default.

## Accessibility

A skeleton is decorative, so announce loading at the **region** level rather than per bar: set `aria-busy="true"` on the loading container, and use `aria-live="polite"` if it's a live region so the change to real content is announced. The placeholder bars carry no text and should not be read out individually.

## Installation

### skeleton

**Install:**

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

**Dependencies:** utils

**Usage & accessibility:** A loading placeholder — a div with bg-accent animate-pulse rounded-md, no dependency. Size it with a className (h-4 w-32, size-10, etc.) to stand in for the content while it loads.

```tsx
// components/ui/skeleton.tsx
import { cn } from "@/lib/utils"

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="skeleton"
      className={cn("bg-accent animate-pulse rounded-md", className)}
      {...props}
    />
  )
}

export { Skeleton }
```

## Examples

### Card skeleton

The canonical skeleton — a circular avatar placeholder beside two text lines.

**Install:**

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

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-demo-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonDemo01() {
  return (
    <div className="flex items-center space-x-4">
      <Skeleton className="size-12 rounded-full" />
      <div className="space-y-2">
        <Skeleton className="h-4 w-[250px]" />
        <Skeleton className="h-4 w-[200px]" />
      </div>
    </div>
  )
}
```

---

### Content card

A media card placeholder — image block, title and body lines, and a footer with a button and avatar.

**Install:**

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

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-card-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonCard01() {
  return (
    <div className="w-full max-w-sm rounded-lg border p-4">
      <Skeleton className="aspect-video w-full rounded-md" />
      <div className="mt-4 space-y-2">
        <Skeleton className="h-4 w-3/4" />
        <Skeleton className="h-4 w-full" />
        <Skeleton className="h-4 w-2/3" />
      </div>
      <div className="mt-4 flex items-center justify-between">
        <Skeleton className="h-8 w-20 rounded-md" />
        <Skeleton className="size-8 rounded-full" />
      </div>
    </div>
  )
}
```

---

### List / feed

A repeating row placeholder — avatar plus two lines — the loading state for feeds and comment lists.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-list-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-list-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonList01() {
  return (
    <div className="w-full max-w-sm space-y-4">
      {Array.from({ length: 5 }).map((_, i) => (
        <div key={i} className="flex items-center gap-3">
          <Skeleton className="size-10 shrink-0 rounded-full" />
          <div className="flex-1 space-y-2">
            <Skeleton className="h-3.5 w-1/2" />
            <Skeleton className="h-3.5 w-4/5" />
          </div>
        </div>
      ))}
    </div>
  )
}
```

---

### Table

A table placeholder — a header row and a grid of cell bars from rows × columns.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-table-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-table-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

const ROWS = 5
const COLS = 4

export default function SkeletonTable01() {
  return (
    <div className="w-full max-w-md overflow-hidden rounded-md border">
      <div className="bg-muted/50 flex gap-4 border-b px-4 py-3">
        {Array.from({ length: COLS }).map((_, i) => (
          <Skeleton key={i} className="h-4 flex-1" />
        ))}
      </div>
      <div className="divide-y">
        {Array.from({ length: ROWS }).map((_, r) => (
          <div key={r} className="flex gap-4 px-4 py-3">
            {Array.from({ length: COLS }).map((_, c) => (
              <Skeleton key={c} className="h-4 flex-1" />
            ))}
          </div>
        ))}
      </div>
    </div>
  )
}
```

---

### Article

A long-form placeholder — heading, byline, paragraph lines, and an inline image block.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-article-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-article-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonArticle01() {
  return (
    <div className="w-full max-w-md space-y-4">
      <Skeleton className="h-8 w-3/4" />
      <div className="flex items-center gap-2">
        <Skeleton className="size-6 rounded-full" />
        <Skeleton className="h-3 w-28" />
      </div>
      <div className="space-y-2">
        <Skeleton className="h-4 w-full" />
        <Skeleton className="h-4 w-full" />
        <Skeleton className="h-4 w-5/6" />
      </div>
      <Skeleton className="aspect-video w-full rounded-lg" />
      <div className="space-y-2">
        <Skeleton className="h-4 w-full" />
        <Skeleton className="h-4 w-11/12" />
        <Skeleton className="h-4 w-2/3" />
      </div>
    </div>
  )
}
```

---

### Dashboard

A dashboard placeholder — a grid of stat cards above a chart block.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-dashboard-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-dashboard-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonDashboard01() {
  return (
    <div className="w-full max-w-md space-y-4">
      <div className="grid grid-cols-2 gap-4">
        {Array.from({ length: 4 }).map((_, i) => (
          <div key={i} className="space-y-2 rounded-lg border p-4">
            <Skeleton className="h-3 w-20" />
            <Skeleton className="h-7 w-24" />
            <Skeleton className="h-3 w-16" />
          </div>
        ))}
      </div>
      <div className="rounded-lg border p-4">
        <Skeleton className="h-3 w-28" />
        <Skeleton className="mt-4 h-40 w-full rounded-md" />
      </div>
    </div>
  )
}
```

---

### Form

A form placeholder — repeated label-and-input pairs plus a submit button.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-form-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-form-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonForm01() {
  return (
    <div className="w-full max-w-sm space-y-6">
      {Array.from({ length: 3 }).map((_, i) => (
        <div key={i} className="space-y-2">
          <Skeleton className="h-3.5 w-24" />
          <Skeleton className="h-9 w-full rounded-md" />
        </div>
      ))}
      <Skeleton className="h-9 w-full rounded-md" />
    </div>
  )
}
```

---

### Profile header

A profile header placeholder — large avatar with name and meta, a row of stat counters, and an action button.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-profile-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-profile-01.tsx
import { Skeleton } from "@/components/ui/skeleton"

export default function SkeletonProfile01() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <div className="flex items-center gap-4">
        <Skeleton className="size-16 shrink-0 rounded-full" />
        <div className="flex-1 space-y-2">
          <Skeleton className="h-5 w-32" />
          <Skeleton className="h-3.5 w-40" />
        </div>
      </div>
      <div className="flex gap-6">
        {Array.from({ length: 3 }).map((_, i) => (
          <div key={i} className="space-y-1.5">
            <Skeleton className="h-5 w-12" />
            <Skeleton className="h-3 w-14" />
          </div>
        ))}
      </div>
      <Skeleton className="h-9 w-full rounded-md" />
    </div>
  )
}
```

---

### Shimmer variant

Swaps the default `animate-pulse` for a gradient sweep via a small `.skeleton-shimmer` utility.

**Install:**

```bash
npx shadcn@latest add @designrevision/skeleton-shimmer-01
```

**Dependencies:** skeleton

```tsx
// components/ui/skeleton-shimmer-01.tsx
import { cn } from "@/lib/utils"

// A shimmer skeleton swaps the default `animate-pulse` for a gradient sweep.
// The `.skeleton-shimmer` utility lives in globals.css.
function Shimmer({ className }: { className?: string }) {
  return <div className={cn("skeleton-shimmer rounded-md", className)} />
}

export default function SkeletonShimmer01() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <Shimmer className="aspect-video w-full" />
      <div className="flex items-center space-x-4">
        <Shimmer className="size-12 rounded-full" />
        <div className="flex-1 space-y-2">
          <Shimmer className="h-4 w-3/4" />
          <Shimmer className="h-4 w-1/2" />
        </div>
      </div>
    </div>
  )
}
```
