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.
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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
Props
No props documented yet.
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>
)
}
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:
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:
<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:
{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:
@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.