Repo

A skeleton stands in for content while it loads. By default it fills its container: the full width, and the full height when the parent has a set height. When the parent has no set height, it is one line tall. Set a size when you want something else.

Usage

<div style="display: flex; gap: var(--space-sm); align-items: center; width: 100%;">
<div class="zui-skeleton zui-skeleton-shape-circle" style="--zui-skeleton-size: 3rem; flex-shrink: 0;" aria-hidden="true"></div>
<div style="flex: 1; display: grid; gap: var(--space-2xs);">
  <div class="zui-skeleton" style="--zui-skeleton-width: 40%;" aria-hidden="true"></div>
  <div class="zui-skeleton" aria-hidden="true"></div>
</div>
</div>

Fill the container

With no size set, a skeleton takes the width and height of its parent. Give the parent a height, and the skeleton fills it. This suits an image slot or a card that must not change size when the real content arrives.

<div style="width: 100%; height: 8rem;">
<div class="zui-skeleton" aria-hidden="true"></div>
</div>

In a flex row, a skeleton stretches to the height of its tallest sibling. Here it matches the height of the text block beside it.

Article title

Two lines of text
set the height.

<div style="display: flex; gap: var(--space-sm); width: 100%;">
<div class="zui-skeleton" style="--zui-skeleton-width: 6rem; flex-shrink: 0;" aria-hidden="true"></div>
<div>
  <h3 style="margin: 0;">Article title</h3>
  <p>Two lines of text<br />set the height.</p>
</div>
</div>

Set a size

Use width, height or size (both at once). A number is pixels. A string is any CSS length. In plain HTML, set --zui-skeleton-width, --zui-skeleton-height or --zui-skeleton-size.

For a circle, set size, or only a width. The height then follows the width, so the circle stays round.

<div style="display: grid; gap: var(--space-xs); width: 100%;">
<div class="zui-skeleton" style="--zui-skeleton-width: 12rem; --zui-skeleton-height: 1.5rem;" aria-hidden="true"></div>
<div class="zui-skeleton" style="--zui-skeleton-width: 60%; --zui-skeleton-height: 0.75rem;" aria-hidden="true"></div>
<div class="zui-skeleton" style="--zui-skeleton-size: 4rem;" aria-hidden="true"></div>
</div>

Aspect ratio

Use aspectRatio when you know the shape but not the size, for example an image. The width fills the container and the height follows.

<div style="width: min(100%, 20rem);">
<div class="zui-skeleton" style="--zui-skeleton-aspect-ratio: 16 / 9; --zui-skeleton-height: auto;" aria-hidden="true"></div>
</div>

Animation

pulse is the default. wave sweeps a highlight across. none is still, for a calm page or a print layout. Motion stops when the user turns on "reduce motion".

<div style="display: grid; gap: var(--space-xs); width: 100%;">
<div class="zui-skeleton" aria-hidden="true"></div>
<div class="zui-skeleton zui-skeleton-animation-wave" aria-hidden="true"></div>
<div class="zui-skeleton zui-skeleton-animation-none" aria-hidden="true"></div>
</div>

Shape

<div style="display: flex; gap: var(--space-sm); align-items: center;">
<div class="zui-skeleton" style="--zui-skeleton-size: 4rem;" aria-hidden="true"></div>
<div class="zui-skeleton zui-skeleton-shape-hard" style="--zui-skeleton-size: 4rem;" aria-hidden="true"></div>
<div class="zui-skeleton zui-skeleton-shape-circle" style="--zui-skeleton-size: 4rem;" aria-hidden="true"></div>
<div class="zui-skeleton zui-skeleton-shape-squircle" style="--zui-skeleton-size: 4rem;" aria-hidden="true"></div>
</div>

Accessibility

A skeleton is decoration. The wrappers always set aria-hidden="true", so screen readers skip it. In plain HTML, add it yourself.

Tell assistive tech about the loading state on the region that holds the skeletons:

<section aria-busy="true">
  <p class="sr-only" role="status">Loading comments</p>
  <div class="zui-skeleton" aria-hidden="true"></div>
  <div class="zui-skeleton" aria-hidden="true"></div>
</section>

In forced-colours mode (Windows High Contrast) each skeleton gets an outline, so it stays visible.

Props

Prop Type Default Description
width string | number fills the container Width. A number is pixels
height string | number fills the container, at least one line Height. A number is pixels
size string | number — Sets width and height together
aspectRatio string | number — For example 16 / 9 or 1. Height follows the width
shape default | hard | circle | squircle default Corner shape
animation pulse | wave | none pulse Loading animation

CSS custom properties

Set these on :root, on a wrapper, or on the element. See Component custom properties.

Property Default Description
--zui-skeleton-width 100% Width
--zui-skeleton-height 100% Height. When the parent has no set height, the skeleton is one line tall
--zui-skeleton-size — Width and height together. width and height win over it
--zui-skeleton-aspect-ratio auto Aspect ratio. Set --zui-skeleton-height: auto with it
--zui-skeleton-radius var(--radius-md) Border radius
--zui-skeleton-bg oklch(from var(--color-text) l c h / 10%) Base colour
--zui-skeleton-highlight oklch(from var(--color-text) l c h / 18%) Sweep colour for the wave animation

Theme

Copy this CSS to your project: