Loading placeholder that preserves layout while content is being fetched. Two presets: Custom (free-form) and Text (tied to typography styles).
v3.1.0
Skeleton
3.1.0
3.1.0
--
--
States
Skeleton has a single visual state — loading. The style (OnWhite or WhiteOverlay) is chosen based on the background it sits on.
OnWhite — on white / light grey backgrounds
-
1
background: --skeleton-on-white
-
2
wave: --skeleton-wave — gradient overlay sweeping left to right
-
3
custom shape: border-radius set by parent component, no override
-
4
text bar: fixed height + border-radius per typography style (see Layout)
WhiteOverlay — on colored or dark backgrounds
-
1
background: --skeleton-white-overlay
-
2
wave: --skeleton-wave (same token, lower opacity on dark)
-
3
use when: skeleton sits on a colored, image, or dark surface
Anatomy
Every skeleton instance — Custom or Text — is built from the same three-layer structure.
.skeleton
.skeleton-animation
.skeleton-gradient-walker
-
1
.skeleton Root element. Sets background color, overflow:hidden, position:relative. Width and height are defined by the parent context.
-
2
.skeleton-animation position:absolute; inset:0; overflow:hidden. Clips the walker to the skeleton boundaries.
-
3
.skeleton-gradient-walker 150px wide gradient strip. Animates translateX from -150px to 100%+150px over 1500ms.
Layout
Text Skeleton bars have fixed heights and border-radii that correspond to each typography style. The wrapper adds vertical padding so the bar aligns correctly within the line-height.
Promo Heading
Heading 1
Heading 2
Heading 3
Heading 4
Promo Body
Main Body
Compact Body
Main Link
Compact Link
Caption
-
1
max 2 lines: Even if real content has 3+ lines, skeleton shows max 2 bars
-
2
width: Side margins within the text container are customizable — set width on the .skeleton element
-
3
no font scaling: Skeleton size is fixed — does not change with accessibility font size increases
-
4
font-agnostic: Shape is locked regardless of the font set in use
Animation
The wave gradient sweeps across the skeleton from left to right (right to left in RTL). Duration is fixed at 1500ms.
LTR — wave sweeps left → right
RTL — wave sweeps right → left
-
1
event: content loading
-
2
pattern: Continuous sweep
-
3
duration: 1500ms
-
4
curve: linear
-
5
iteration: infinite
-
6
RTL: [dir="rtl"] .skeleton-animation { transform: scaleX(-1); }
-
7
reduced-motion: animation disabled — background color only
Usage
✅ Use skeleton when
Content takes more than 300ms to load. Use skeleton to hold layout and prevent shift.
Replacing known content shape: text, images, squircles, cards.
❌ Do not use skeleton when
Load time is under 300ms — no skeleton needed, content appears fast enough.
Shape is unknown — use a generic spinner instead.
Accessibility
Skeleton is a decorative loading state: it announces that content is coming, once, without interrupting whatever the screen reader is already saying.
1
2
-
1
The container announces, once role="status" with aria-label="Loading" and aria-live="polite". Polite rather than assertive: the announcement waits for a gap instead of cutting into ongoing speech.aria-busy="true" on the region being loaded, not on the shapes.When the real content arrives, drop aria-busy and take the skeleton out of the DOM. Leaving it hidden but present keeps a stale live region on the page.
-
2
A shape is never a target pointer-events: none and user-select: none on .skeleton and .skeleton-text.No tabindex on any skeleton element. A placeholder that takes focus offers a control that does not exist yet.
-
Reduced motion The wave stops entirely — var(--pattern-shimmer) resolves to none. The background colour alone carries the loading state, so nothing is lost by removing the movement.
-
Low contrast, on purpose var(--skeleton-on-white) against var(--background-primary) sits far below the text floor, and so does var(--skeleton-white-overlay) on a colour. WCAG 1.4.3 exempts decorative elements; the purpose is carried by aria-label, not by the contrast.