Skeleton
Hero example
When to use
Reach for Skeleton when a piece of content has a known shape and a measurable load time. A skeleton matches the eventual layout, so the surrounding chrome does not shift on resolve, and the user sees that something is loading without reading a label.
Do not use Skeleton for content that resolves under one frame. Do not use it for content that may never load (use an empty state instead). For full-page loading inside Next.js App Router, compose Skeletons under PageSkeleton and drop the result into a loading.tsx Suspense boundary.
Anatomy
A single <div aria-hidden> with the .m-skeleton recipe. The shimmer sweep lives on an ::after pseudo-element so the track and the highlight can transition independently. Width and height pass through as inline style; the radius and static props surface as data-* attributes so CSS can switch shapes without re-rendering.
The track color reads from --stone-200. The highlight sweep reads from --stone-300 and travels left to right every 3 seconds with linear motion, matching the canonical Matter shimmer cadence used by Pill shimmer and the marquee text reveals.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| width | number | string | — | — |
| height | number | string | — | — |
| radius | SkeletonRadius | "md" | — |
| static | boolean | — | Renders the track without the shimmer sweep. Use when many skeletons stack on one page — keep one or two animated, the rest static, so the surface doesn't strobe. |
| className | string | — | — |
| style | React.CSSProperties | — | — |
Also accepts Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "style">.
Composition
Two thin composers ship alongside the primitive:
- SkeletonText renders N stacked single-line Skeletons. The last line narrows to 60 percent by default so a paragraph placeholder reads as prose rather than bars.
- SkeletonAvatar renders a circular Skeleton sized to match the Avatar atom — 24, 32, or 48 pixels.
import { Skeleton, SkeletonAvatar, SkeletonText } from "@matter/components";
export function PersonRowSkeleton() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<SkeletonAvatar size="md" />
<div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 6 }}>
<Skeleton height={14} width={180} />
<Skeleton height={10} width={120} />
</div>
<Skeleton height={22} radius="pill" width={72} />
</div>
);
}States
Skeleton has two states, controlled by the static prop:
- default — the shimmer sweep animates continuously
- static — only the track renders; useful when a single page stacks many Skeletons and a wall of animated highlights would strobe
Themes
Tokens consumed
--stone-200for the track background--stone-300for the highlight sweep--radius-sm,--radius-md,--radius-lgfor the fourradiusvariants (pillresolves to 999px)
Accessibility
- Keyboard interactions. None. Skeleton is presentational; focus belongs on the resolved content once it mounts.
- ARIA roles and properties. Each Skeleton renders
aria-hidden. Wrap stacks in arole="status"region witharia-busy="true"andaria-label="Loading…"so assistive tech announces the load once per region, not once per primitive. PageSkeleton does this for you. - Reduced motion. Under
prefers-reduced-motion: reduce, the shimmer sweep stops; the track stays visible so the placeholder shape still communicates loading. - Forced colors. Under
forced-colors: active, the track switches toGrayTextand the gradient sweep is hidden.
Do / Don't
static on the rest.role="status" region (or use PageSkeleton) so screen readers announce one load, not many.Loading… in sentence case.Recipes
- A list-row placeholder uses one 14-pixel-tall Skeleton for the title, one 10-pixel-tall Skeleton for the meta, and a 22-pixel pill-radius Skeleton for a trailing chip.
- A card placeholder stacks a 14-pixel header skeleton, an 18-pixel title, and four rows of body skeletons.
- A grid of stat tiles uses 120-pixel-tall Skeletons with
radius="lg".
Code example
import { Skeleton } from "@matter/components";
export function StatTile() {
return <Skeleton height={120} radius="lg" />;
}Source
packages/components/src/SkeletonEndpointBadge
Translucent pill labelling an HTTP endpoint — verb (method-colored) and path (mono). Omit the path for verb-only mode (the V1 VerbPill role).
PageSkeleton
Layout-aware loading placeholder. Eight presets that mirror the dashboard's content shapes — list, grid, detail, two-pane, three-pane, tabs-content, form, console.