Skeleton

Holds the shape of content while it loads.

States

  • box

  • avatar

  • label

  • body, three lines

Usage

When to use it
Use
A list, a card or a number loads on first view and its shape is known.
Use
The page should not move when the content lands.
Not for
Content already on screen is being refreshed; keep it and mark it busy. Use Progress.
Not for
A button's action is running. Use Button.
Not for
Nothing came back. Use Empty state.

Rules

6 rules
Never
Animate a Skeleton with a pulse or a sweep. Because the pending arc is Glaze's one loop, and a second one draws the eye to the part of the page with nothing in it.
Must
Show a skeleton through SkeletonSwap, never with loading ? <Skeleton/> : content. Because SkeletonSwap waits 150ms and then holds 400ms, so a fast response never blinks the skeleton on and off.
Must
Shape the skeleton like the content: the same component, sizes and SkeletonText type roles. Because a skeleton of another height makes the page jump when the content lands, which is the one thing it is for.
Must
Pass label to SkeletonSwap naming what loads: "Loading runs". Because the skeleton is hidden from assistive tech, so the label is all a screen reader hears while it shows.
Never
Replace content the person is reading with a skeleton on a refresh. Because the text they were reading vanishes; SkeletonSwap keeps it, marks it busy and dims it after 150ms.
Should
Round a Skeleton that stands in for a person's avatar with rounded-pill; everything else keeps the tag corner.

Accessibility

3 checks
Skeletons are aria-hidden; SkeletonSwap sets aria-busy and speaks its label once in a status region.
A reload keeps the content readable and marks it aria-busy.
Forced colours fill skeletons with GrayText.

Edge cases

6 edge cases
Response under 150ms
The skeleton never shows; the space is held and the content swaps in.
Response at 170ms
The skeleton shows and stays to 400ms, so it does not blink.
Refresh with content shown
The content stays, busy, and dims after 150ms.
Request fails
Set loading false and render an Empty with reason="error" as the content.
Reduced motion
Nothing changes: the skeleton is already still.
Dark theme
The field flips with the tokens.

API

9 props
Skeleton className
string
SkeletonText type
'heading' | 'heading-sm' | 'body' | 'body-sm' | 'label' | 'label-sm' | 'caption' | 'code' | 'code-sm' default 'body'
SkeletonText lines
number default 1
SkeletonText last
number default 0.6
SkeletonSwap loading
boolean
SkeletonSwap fallback
ReactNode
SkeletonSwap delay
number default --wait-show (150)
SkeletonSwap minDuration
number default --wait-hold (400)
SkeletonSwap label
string default 'Loading'

Tokens

8 tokens
--field
#efefef
--radius-tag
6px
--dur-state
160ms
--wait-show
150ms
--wait-hold
400ms
--stale
0.6
--type-body
400 14px/1.5 var(--font-sans)
--type-label
500 13px/1.35 var(--font-sans)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.