Skeleton

A pulsing placeholder shown while content is loading. Pair with explicit dimensions.

Profile row

Card

Paragraph

Installation

pnpm
pnpm dlx @hex-core/cli add skeleton

List item skeleton

Repeated avatar-plus-text rows for a feed or contacts list

tsx
<div className="space-y-4" aria-busy="true" aria-live="polite">
  {Array.from({ length: 3 }, (_, i) => (
    <div key={i} className="flex items-center gap-4">
      <Skeleton className="h-10 w-10 rounded-full" />
      <div className="flex-1 space-y-2">
        <Skeleton className="h-4 w-[60%]" />
        <Skeleton className="h-3 w-[40%]" />
      </div>
    </div>
  ))}
</div>

Table row skeleton

Real <thead> with skeleton <tbody> cells — the canonical loading state for the data-table

tsx
<table className="w-full border-separate border-spacing-y-2 text-sm" aria-busy="true">
  <thead>
    <tr className="text-left text-muted-foreground">
      <th className="font-medium">Name</th>
      <th className="font-medium">Email</th>
      <th className="font-medium">Role</th>
    </tr>
  </thead>
  <tbody>
    {Array.from({ length: 4 }, (_, i) => (
      <tr key={i}>
        <td><Skeleton className="h-4 w-32" /></td>
        <td><Skeleton className="h-4 w-48" /></td>
        <td><Skeleton className="h-4 w-20" /></td>
      </tr>
    ))}
  </tbody>
</table>

API Reference

PropTypeDefaultDescription
className
stringWidth/height and any additional styling via Tailwind classes

AI Guidance

When to use

Use during async data loads to show the shape of forthcoming content. Match the dimensions and layout of the real content to avoid layout shift on load.

When not to use

Don't use for fast operations (<200ms — users prefer a brief spinner or nothing). Don't use as a permanent empty state (use proper empty-state UI).

Common mistakes

  • Skeleton dimensions don't match loaded content — causes layout shift
  • Leaving Skeleton visible for long loads without a timeout/retry
  • Using Skeleton for interactive elements users might tap

Accessibility

Add aria-busy='true' on the loading container and a visually hidden status (aria-live='polite') to announce load completion to screen readers.

Related components

Token budget: 200