Skip to content

Skeleton loader

In review
Live · web
Loading content…

Primitive shapes: block, avatar, thumb, button, chip

Composed skeletons: same footprint as the real component

State: skeleton · motion shimmer. Toggle it; the footprint holds, so the layout never jumps.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
// Mimic the FOOTPRINT of what is loading, so nothing shifts when data lands.
// A skeleton is NOT a spinner and NOT a progress bar; it has the content's shape.
function SessionList({ loading, sessions }: Props) {
if (loading) {
return (
<div aria-busy aria-live="polite">
{Array.from({ length: 4 }).map((_, i) => (
<SessionRowSkeleton key={i} /> // avatar + two lines + trailing value
))}
</div>
);
}
return <>{sessions.map((s) => <SessionRow key={s.id} {...s} />)}</>;
}
// Primitive shapes compose any skeleton. `motion="pulse"` swaps the sweep for a
// calm opacity pulse; both freeze under prefers-reduced-motion.
<Skeleton shape="avatar" size={28} />
<Skeleton shape="thumb" radius="card" />
<SkeletonText lines={3} /> {/* the last line renders shorter */}
<Skeleton shape="button" motion="pulse" />
PropTypeDefaultDescription
shape'line' | 'avatar' | 'thumb' | 'button' | 'chip''line'The placeholder silhouette. Its radius matches the real thing it replaces: avatar/chip pill, thumb card radius, button control radius, line the track radius.
motion'shimmer' | 'pulse''shimmer'Animation style. shimmer sweeps a token highlight across the block (transform only); pulse animates opacity. Both freeze under prefers-reduced-motion.
widthnumber | stringBlock width (px or any CSS length). Lines accept percentages; the last line of a SkeletonText is rendered shorter automatically.
sizenumberConvenience for square shapes (avatar): sets both width and height.
radius'track' | 'control' | 'card' | 'pill'Override the shape's default radius token when composing a custom footprint.
linesnumber1On SkeletonText: how many stacked lines to render, with a consistent gap and a shorter final line.

The skeletons are presentational (aria-hidden). Wrap the loading region in one element with aria-busy + aria-live=“polite” so assistive tech hears a single “Loading” status, not every empty box.