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.

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

utils

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

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

Packages

skeleton

Props

No props documented yet.

Copied!
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>
  )
}

Frequently asked questions

A skeleton loader (or skeleton screen) is a placeholder that mimics the shape of your content — boxes and bars where text, images, and avatars will appear — shown while data loads. It sets the layout immediately, so the page does not jump when content arrives and the wait feels shorter than a blank screen or a spinner.
It is a one-line primitive: a div with bg-accent animate-pulse rounded-md and no dependency. You size and shape each piece with a className and compose those pieces into card, list, table, article, dashboard, form, or profile skeletons. Every example on this page is built from that single component.
Render skeleton pieces while loading, then swap them for real content: {isLoading ? : }. Each piece is just sized to match what it stands in for. Mirror the real layout — same spacing, same number of lines — so nothing shifts when the data arrives. The examples here cover the common layouts.
No. react-loading-skeleton is a separate package, but the shadcn Skeleton is a single div you already have once you add it — no extra dependency, and full control with Tailwind. It does the same job: size pieces to your layout, and use the Shimmer variant on this page if you want the moving-gradient look.
With a className. Set the height and width (h-4 w-3/4, h-9 w-full, size-12), and the radius for the shape — rounded-md for bars and blocks, rounded-full for avatars. Match each piece to the real element it replaces so the placeholder and the final content occupy the same space.
The default animation is animate-pulse (a fade in and out). For a moving shimmer, swap it for a gradient that sweeps across — add a small .skeleton-shimmer utility (a linear-gradient with an animated background-position) and apply it instead of the pulse. The Shimmer variant example includes the utility and the markup.
Use a skeleton when you know the shape of what is loading (a card, a list, a table) — it reserves the layout and reads as faster. Use a spinner for short, indeterminate waits where the result has no predictable shape (a form submit, a quick action). For full-page and content-heavy loads, skeleton screens are the better default.
Treat a skeleton as decorative and announce loading at the region level: put aria-busy="true" on the container while loading and, if it is a live region, aria-live="polite" so screen readers hear when content is ready. The placeholder bars themselves carry no text, so they should not be announced individually.