Anatomy
loading loaded
┌──────────────────────┐ ┌──────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ │ │ Riverside Fit-Out │
│ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓ │ ───▶ │ Dana · $8,400 · 12d │
│ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓ │ │ Kestrel Joinery │
└──────────────────────┘ └──────────────────────┘
same positions, same sizes — nothing moves on arrival
- Same geometry as the real thing. If the layout shifts when content arrives, the skeleton has failed at the one job a spinner cannot do.
- A delay before appearing. Under ~300ms a skeleton is a flicker, which reads as a glitch.
- No fake content. Grey blocks, never placeholder words or numbers — people read them, and briefly believe them.
- A real terminal state. It must resolve to content, an empty state, or an error. Never to itself.
Why it works
A spinner says something is happening. A skeleton says this specific thing is happening, here, and it will look like this. That converts an ambiguous wait into an oriented one, and orientation is what makes waiting tolerable.
It also removes layout shift, which is a real cost rather than a cosmetic one: content that jumps causes mis-clicks and forces the eye to re-find its place.
The perceived-speed effect is genuine but secondary. The reason to prefer a skeleton is that it is honest about structure — it shows the shape of what is coming rather than an abstract indicator of activity.
The three states it must not confuse
This is the pattern's real risk. Loading, empty and broken must look different.
- A skeleton that never resolves reads as loading forever, and people wait instead of reporting.
- A skeleton replaced by nothing reads as breakage.
- An empty state shown during loading tells someone their data is gone.
Decide all three renders up front. Most implementations build only the first.
Getting it wrong
- Flashing on fast responses, because there is no delay before showing.
- A skeleton that does not match the layout, so everything jumps on arrival.
- Fake text or numbers, briefly read as real.
- Skeleton plus spinner, which is two answers to one question.
- Shimmer with no reduced-motion fallback, which is a genuine accessibility problem for a decorative effect.
- No failure path, leaving a grey page forever when the request errors.
Exemplars
Facebook popularised the pattern, and the reason it stuck is the geometry match: the skeleton is the post layout, so the feed never jumps.
LinkedIn and Slack both delay the skeleton, which is the detail most implementations skip and the one that separates "feels fast" from "feels glitchy".
GitHub is a good counter-example of restraint: most navigations are fast enough that no placeholder appears at all, which is the correct choice when the wait is short.
The extractable rule: a skeleton is a promise about layout. Keep the promise — same shapes, same places — or you have added a flicker and a jump to a screen that was merely slow.