Skip to main content

Installation

npx shadcn@latest add skeleton

Usage

import { Skeleton } from "@/components/ui/skeleton"
<Skeleton className="h-[20px] w-[100px] rounded-full" />

Component API

Skeleton

Animated loading placeholder component.
function Skeleton({ className, ...props }: React.ComponentProps<"div">)
Features:
  • Pulse animation
  • Customizable size and shape
  • Accessible loading indicator

Implementation

Simple div with pulse animation:
<div
  className="animate-pulse rounded-md bg-accent"
  {...props}
/>

Examples

Basic Skeleton

Simple loading placeholder:
<div className="space-y-2">
  <Skeleton className="h-4 w-[250px]" />
  <Skeleton className="h-4 w-[200px]" />
</div>

Avatar Skeleton

Circular avatar placeholder:
<div className="flex items-center space-x-4">
  <Skeleton className="h-12 w-12 rounded-full" />
  <div className="space-y-2">
    <Skeleton className="h-4 w-[250px]" />
    <Skeleton className="h-4 w-[200px]" />
  </div>
</div>

Card Skeleton

Card with loading content:
<Card>
  <CardHeader>
    <Skeleton className="h-8 w-[200px]" />
    <Skeleton className="h-4 w-[300px]" />
  </CardHeader>
  <CardContent className="space-y-2">
    <Skeleton className="h-4 w-full" />
    <Skeleton className="h-4 w-full" />
    <Skeleton className="h-4 w-2/3" />
  </CardContent>
</Card>

Text Skeleton

Multiple text lines:
<div className="space-y-3">
  <Skeleton className="h-5 w-2/5" />
  <Skeleton className="h-4 w-4/5" />
  <Skeleton className="h-4 w-full" />
  <Skeleton className="h-4 w-3/5" />
</div>

Form Skeleton

Form field placeholders:
<div className="space-y-4">
  <div className="space-y-2">
    <Skeleton className="h-4 w-[100px]" />
    <Skeleton className="h-10 w-full" />
  </div>
  <div className="space-y-2">
    <Skeleton className="h-4 w-[100px]" />
    <Skeleton className="h-10 w-full" />
  </div>
  <Skeleton className="h-10 w-[100px]" />
</div>

Table Skeleton

Table with loading rows:
<div className="space-y-2">
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
  <Skeleton className="h-10 w-full" />
</div>

Image Skeleton

Image placeholder:
<Skeleton className="h-[300px] w-full rounded-lg" />

Button Skeleton

Button placeholder:
<Skeleton className="h-10 w-[120px] rounded-md" />

List Skeleton

List item placeholders:
<div className="space-y-4">
  {Array.from({ length: 5 }).map((_, i) => (
    <div key={i} className="flex items-center space-x-4">
      <Skeleton className="h-12 w-12 rounded-full" />
      <div className="space-y-2">
        <Skeleton className="h-4 w-[250px]" />
        <Skeleton className="h-4 w-[200px]" />
      </div>
    </div>
  ))}
</div>

Custom Shapes

Different shapes and sizes:
<div className="flex space-x-4">
  <Skeleton className="h-24 w-24 rounded-full" />
  <Skeleton className="h-24 w-24 rounded-md" />
  <Skeleton className="h-24 w-24 rounded-lg" />
  <Skeleton className="h-24 w-24" />
</div>

Conditional Rendering

Show skeleton while loading data:
function UserProfile() {
  const { data, isLoading } = useUser()

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

  return (
    <div className="flex items-center space-x-4">
      <Avatar src={data.avatar} />
      <div>
        <p className="font-medium">{data.name}</p>
        <p className="text-sm text-muted-foreground">{data.email}</p>
      </div>
    </div>
  )
}

Accessibility

  • Uses aria-busy="true" and aria-live="polite" implicitly through loading state
  • Semantic loading indication
  • No interactive elements

Build docs developers (and LLMs) love