Skip to content

Skeleton

<Skeleton> is a single rounded rectangle with a subtle shimmer. Compose several of them to mimic the layout that’s about to render — never to fill empty space.

import from "@na/ui/components/skeleton" ▶ Open in Storybook packages/ui/src/components/skeleton.tsx

When to use

  • The first paint of a list, card, or detail page that’s fetching its data.
  • Anywhere the user expects content to appear in a known shape within ~1 second.

Don’t use Skeleton for: long fetches (≥ 3s should show progress, not pulse), zero-results (use EmptyState), or for purely decorative placeholders that never get replaced.

Single shape

<Skeleton className="h-4 w-3/5" />
<Skeleton className="h-4 w-4/5" />
<Skeleton className="h-4 w-2/5" />

Card placeholder

Mimic the shape of the card that’s coming, not a generic gray block.

<div className="rounded-md border p-4">
<div className="flex items-center gap-3">
<Skeleton className="size-10 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-20" />
</div>
</div>
<div className="mt-4 space-y-2">
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-5/6" />
</div>
</div>

API

Prop Type Default Description
className string Width, height, border-radius — every visible attribute. Required in practice.
...rest HTMLAttributes<HTMLDivElement> Forwarded native attributes.

Design guidelines

✓ Do

  • Match the skeleton to the real content layout — a 4-line description gets 4 line skeletons.
  • Pulse stays during the first paint only. Replace with real content as soon as the fetch resolves.
  • Use rounded-full for circular avatars, default for everything else.

✗ Don't

  • Use Skeleton for fetches longer than 3 seconds. That's a Loader / Progress moment.
  • Animate skeletons across page transitions. They are paint-and-replace, not a permanent state.
  • Stack 12 identical bars. Pick a representative shape and render 3–5 instances of it.

Accessibility

  • aria-hidden is implied via the structure — the skeleton is decorative; the real content carries the meaning.
  • For long-running fetches that genuinely need an announcement, wrap the region in aria-busy="true" and switch to aria-busy="false" on completion.
  • Loader — Indeterminate spinner overlay for blocking operations.
  • Progress — Determinate progress bar.
  • EmptyState — When the fetch resolves to zero rows.

▶ Open Skeleton stories in Storybook