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 classNamestringSkeletonText type'heading' | 'heading-sm' | 'body' | 'body-sm' | 'label' | 'label-sm' | 'caption' | 'code' | 'code-sm'default'body'SkeletonText linesnumberdefault1SkeletonText lastnumberdefault0.6SkeletonSwap loadingbooleanSkeletonSwap fallbackReactNodeSkeletonSwap delaynumberdefault--wait-show (150)SkeletonSwap minDurationnumberdefault--wait-hold (400)SkeletonSwap labelstringdefault'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)