Components
Skeleton
Loading placeholders. The shimmer is legacy's one skeleton — a 200%-wide gradient sweeping across a tinted ground — and everything here is that same treatment cut to different shapes. Plain server components: a skeleton that needed hydration before it could paint would defeat the point.
The rule
Mirror the real layout, don't approximate it. A skeleton whose rows are a
different height than the content that replaces them produces a jump on load,
which is worse than no skeleton — so each page's skeleton lives beside the
layout it stands in for and reuses the same spacing classes. SkeletonLine's
width exists for the same reason: real copy is ragged, and equal-length bars
read as a table rather than as prose.
import { Skeleton, SkeletonLine } from "@/components/base/skeleton";
<Skeleton className="size-10 rounded-full" />
<SkeletonLine width="72%" />
<SkeletonLine width="48%" />Usage
Skeleton is the shimmer as a block; the exported SHIMMER class is the same
treatment for places that cannot take a <div> — a placeholder inside a
<button>, which is phrasing content only. One definition, so a hand-rolled
span cannot drift from the component. The gradient carries its own ground: its
outer stops are the ground tone and the tile repeats, so no caller has to
remember to paint a plate underneath.
Theming
The stops are the --skeleton / --skeleton-sheen tokens, stated per theme
the way the ink ladder is: light needs the heavier ground to sit as far off
its canvas as dark's does off its own. It used to be a fixed white tint — a
sweep that only lifts a dark ground — and every skeleton on the site was
invisible in light mode. The sheen is the brighter stop in both themes, which
is why the alphas invert.
Accessibility
SkeletonScreen wraps a group so assistive tech announces the wait
(role="status" aria-busy) instead of reading a screenful of empty boxes;
each shape is aria-hidden. label is optional because a route-level
loading.tsx cannot await a translation — omitted, the busy state still
carries.