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.
<SkeletonBlock className="flex max-w-sm gap-3">
<SkeletonCircle size={40} />
<SkeletonText lines={3} className="flex-1" />
</SkeletonBlock>Loading
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.
<SkeletonBlock aria-label="Loading your feed">
<SkeletonToolbar />
<SkeletonCardList count={4} />
</SkeletonBlock>Loading