Skeleton

.skeleton

Skeleton — the grey shapes that hold a piece of layout open while its content is still loading.

Examples

Reserving the space content will take

View “Reserving the space content will take” as

Loading the article…

<section class="card" style="inline-size: 22rem" aria-busy="true">
  <p class="sr-only" role="status">Loading the article…</p>
  <div class="card-body" aria-hidden="true">
    <div class="skeleton skeleton-block"></div>
    <div class="skeleton-lines">
      <span class="skeleton skeleton-text"></span>
      <span class="skeleton skeleton-text"></span>
      <span class="skeleton skeleton-text"></span>
    </div>
  </div>
</section>

Shown inside the card it stands in for, because that is the only way to see it: a skeleton is inline-size: 100% and reserves the space its content will take, so on its own in a centred row it has no width to fill and paints nothing.

It also reads --color-surface-sunken, which is invisible against a sunken surface — put skeletons where the content goes, not on the page background. The shapes are aria-hidden and the state lives on the region as aria-busy; a screen reader reading eight empty grey boxes is worse than silence, and aria-busy announces nothing on its own, which is why the role="status" line is there to say what is happening.

Tokens 8

Level 2, declared on .skeleton itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by skeleton
TokenDefault
--skeleton-duration1.6s
--skeleton-line-height1.5em
--skeleton-bgvar(--color-surface-sunken)
--skeleton-radiusvar(--radius-sm)
--skeleton-sheenvar(--color-border)
--skeleton-aspect-ratio16 / 9
--skeleton-circle-size2.5rem
--skeleton-text-thickness0.85em

Variants and states

Variants

  • .skeleton-text
  • .skeleton-block
  • .skeleton-circle

Inside it

  • .skeleton-lines

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • data-legacy

Before you ship it

What you have to do 3 requirements

  1. Put aria-hidden="true" on the outermost purely decorative wrapper, not on each bar. Never on a wrapper that also holds real content or a focusable control: that is an axe failure and a keyboard trap.
  2. Put aria-busy="true" on the region being filled — the element that will still be there when the content arrives — and remove it in the same update that swaps the skeletons for the content.
  3. Say what is loading. The shapes announce nothing by design, so the region needs an .sr-only role="status" that does: "Loading posts…".

src/css/components/skeleton.css · npx mostlycss add skeleton