⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Skeleton

Hold the layout while the content loads

Website / Skeleton
Download .md file
Open .md in new tab
On this page
  • Basic usage
  • Form presets
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product

A skeleton is the shape of content that has not arrived yet: neutral blocks holding the layout so nothing jumps when the real thing lands. is the primitive — a pulsing placeholder block — and the presets shape it to common controls by composing it.

Reach for a skeleton when the incoming content has a known shape (a list, a form, a card). It answers "something is coming, and it will look like this", which is more than a spinner says. For work with a known completion percentage, use progress instead.


Basic usage

carries the fill, the radius and the pulse. It has no size of its own — the composer sizes it, the way the content it stands in for would be sized.

<div class="skeleton-line" style="height: 1em; width: 80%;"></div>
<div class="skeleton-line" style="height: 1em; width: 100%;"></div>
<div class="skeleton-line" style="height: 1em; width: 60%;"></div>

In production, size skeletons from your own stylesheet rather than inline styles — a skeleton for a card title is part of the card's CSS, sized to the line it replaces.


Form presets

Two presets compose the primitive into form shapes: matches a field label, matches a text input's rendered box. Both go on the same element as .

<div class="skeleton-line skeleton-label"></div>
<div class="skeleton-line skeleton-input"></div>

is 44px tall because that is the input's rendered box — it tracks the control it stands in for, not the hit-area floor. A skeleton is never a pointer target, so --target-min does not apply.


Accessibility

  • A skeleton is decoration: give the loading region aria-busy="true" and let the skeleton blocks themselves stay unannounced — they contain no text, so screen readers pass over them
  • Do not rely on aria-busy alone — support is inconsistent, so a user arriving mid-load can meet silence. Pair it with a visually-hidden "Loading" text node in the region (or a polite live-region announcement when loading starts and ends), removed with the skeletons
  • Remove aria-busy and the hidden text when the content lands, so the region reads normally again
  • Under reduced motion the pulse stops and the blocks hold still — a static placeholder still says "loading". The hard requirement behind the pulse is WCAG 2.2.2 (Level A): blinking content lasting longer than five seconds needs a way to stop, which is why skeletons must resolve rather than pulse indefinitely — see the usage rules
  • In forced-colors mode the fill flattens to Canvas, so each block keeps a GrayText border — the loading shape survives High Contrast

Usage rules

Do:

  • Match the skeleton's shape to the content that will replace it — same heights, same widths, same rhythm
  • Set aria-busy="true" on the region being loaded
  • Swap the skeleton for real content in place, so nothing reflows

Don't:

  • Don't use a skeleton for operations with a known percentage — that is progress's job
  • Don't leave skeletons pulsing indefinitely on failure — resolve to an error state that says what happened. This is the WCAG 2.2.2 mechanism, not just courtesy: blinking content that runs past five seconds needs a way to stop
  • Don't stack more than a screenful of skeleton blocks; hold the visible viewport and let the rest arrive unannounced

CSS reference

This section documents how the component is built. For usage, see the sections above.

Tokens

Token Default Purpose
Placeholder fill
Placeholder corner radius
One pulse cycle

Selectors

Selector Purpose
Base primitive: fill, radius, pulse animation
Label preset: 14px tall, 30% wide, label margin
Input preset: input-height block, full width
@keyframes skeleton-pulse The pulse: opacity 1 → 0.4 → 1

Use in another product

The design system installs once per product:

npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0

This component's styles ship in design-system.css. No JavaScript, nothing else to include.

On this page
  • Basic usage
  • Form presets
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product
Previous Cell Input
Next BD Audio

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default