Skeleton
A layout placeholder rendered while content loads. Composed by the consumer to approximate the shape of the content that will replace it.
When to use
- The page is loading layout-significant content (cards, lists, paragraphs) and the wait is long enough that the user would otherwise see blank space.
- You want to communicate "structure exists, content is coming" rather
than "operation in progress" — for the latter, prefer
Spinner.
Skeletons work best when the placeholder shape closely matches the final content. A skeleton that looks nothing like the loaded result disorients more than it reassures.
Variants
| Variant | Class | Use for |
|---|---|---|
| Default | .skeleton |
Generic rectangle, button-like radius |
| Circle | .skeleton.-circle |
Avatars, circular icons |
| Rect | .skeleton.-rect |
Cards, large image placeholders |
| Pill | .skeleton.-pill |
Tags, pill-shaped buttons |
| Text | .skeleton.-text |
Single line of body text (height fixed) |
| Muted | .skeleton.-muted |
De-emphasized on raised surfaces |
Modifiers compose with sizing. Width and height are set inline by the consumer because they depend on the final layout.
Anatomy
<!-- Single line of text -->
<div class="skeleton -text" style="inline-size: 80%;"></div>
<!-- Three lines, last one shorter -->
<div class="skeleton -text" style="inline-size: 100%; margin-block-end: 0.5rem;"></div>
<div class="skeleton -text" style="inline-size: 95%; margin-block-end: 0.5rem;"></div>
<div class="skeleton -text" style="inline-size: 60%;"></div>
<!-- Avatar + name pair -->
<div style="display:flex; gap:1rem; align-items:center;">
<div class="skeleton -circle" style="inline-size: 2.5rem; block-size: 2.5rem;"></div>
<div style="flex: 1;">
<div class="skeleton -text" style="inline-size: 40%; margin-block-end: 0.5rem;"></div>
<div class="skeleton -text" style="inline-size: 25%;"></div>
</div>
</div>
<!-- Card -->
<div class="skeleton -rect" style="inline-size: 100%; aspect-ratio: 16/9;"></div>
Tokens used
From semantic tier
--color-surface-raised— base color--color-surface-canvas— highlight color (alternates with base in shimmer)--radius-button— default radius--radius-card— rect variant radius--radius-pill— pill variant radius--radius-tag— text variant radius--font-body-regular-size— text variant height
Component-tier (defined inline)
--skeleton-base— overridable base color--skeleton-highlight— overridable highlight color--skeleton-duration— overridable shimmer cycle
Accessibility
Skeletons are purely visual placeholders and should not be announced
by screen readers. They lack role="status". The parent container
that swaps skeletons for content should manage announcements (often
via aria-live or aria-busy on the loading region).
For sustained loading where ARIA feedback matters, pair a skeleton
group with a single aria-live="polite" region that announces "Loading"
when loading begins and the loaded content when it arrives.
The component honors prefers-reduced-motion: reduce by removing the
shimmer animation and rendering a flat tone.
Edge cases
- Width and height: the component does not set dimensions. Authors size each skeleton inline (or via a parent layout that stretches it). This is intentional — placeholder dimensions track the final content layout.
- Aspect ratios: combine
aspect-ratiowithinline-size: 100%for responsive media placeholders. - Nested skeletons: a card skeleton may contain text-line
skeletons inside. The animation cycle is independent per element,
which can look busy with many skeletons; consider using
.-mutedfor nested instances to reduce visual noise.
Do
- Match the placeholder layout to the final content shape.
- Use
.-textfor any single line of text — the height token keeps vertical rhythm stable. - Group related skeletons (avatar + name lines) so they read as one loading unit.
Don't
- Don't apply skeletons over content the user can already see. Use them only for layout that hasn't rendered yet.
- Don't add semantic ARIA roles to skeletons. They are decorative.
- Don't keep skeletons visible for sub-100ms loads. Render directly for fast paths and reserve skeletons for waits where the user would otherwise see blank space for more than a moment.