Skeleton

Pulsing placeholder for a layout whose shape you already know, so nothing jumps when the data lands. SkeletonLine, SkeletonCircle and SkeletonText compose it; SkeletonCardList, SkeletonTable, SkeletonRows, SkeletonToolbar and SkeletonPanel are the whole shapes. Wrap a region in SkeletonBlock so it announces itself once instead of not at all. Stops animating under reduce-motion.

Playground

<Skeleton className="size-10 rounded-full" />
<Skeleton className="h-4 w-40" />
<Skeleton className="h-3 w-64" />

Composed

The same shapes built from the primitives. SkeletonBlock is what announces the region as loading, the shapes inside are aria-hidden, so without it a screen reader hears nothing.

Loading
<SkeletonBlock className="flex max-w-sm gap-3">
  <SkeletonCircle size={40} />
  <SkeletonText lines={3} className="flex-1" />
</SkeletonBlock>

Whole shapes

The composites, and the reason they live here rather than in the app: one of them painted a hardcoded white card as an inline style, which no dark selector can reach, and shipped eight of them onto a near-black page for months. Nothing in the design system showed them, so there was nothing to notice it against. Toggle the preview to dark, that is the whole point of this story.

shape
Loading
<SkeletonBlock aria-label="Loading your feed">
  <SkeletonToolbar />
  <SkeletonCardList count={4} />
</SkeletonBlock>