Skeleton UI
Concept
A loading treatment that draws grey blocks where the incoming content will sit, instead of showing a spinner.
Why it exists
For a long time, saying "this is loading" meant one spinning circle. A circle in the middle of the screen, cleared away when the data lands — and it tells you neither how much longer nor what is coming.
Pagedeck's document list showed what that costs. Opening the list took about two seconds, and the circle spun in the middle of an otherwise blank area. When the data landed the list filled in all at once and the page height changed, so the item someone was reaching for shifted and a different document opened. The support messages were not about slowness; they were about pressing the wrong thing.
If the two seconds cannot be removed, what is left is making those two seconds read differently and keeping the page from jumping when the content lands. This treatment does both at once.
How it works
Grey blocks are drawn where the incoming content will sit, and the data fills those same positions when it arrives. Five things hold it together.
- Draw it at the real shape and size. A skeleton shorter than the real card, or a different count, makes the page jump anyway when it fills, which leaves you where the spinner was.
- Use roughly the real number of blocks. If a list usually returns five items, draw five. Fill the whole screen and a short result looks like an error as the page shrinks.
- Add a faint motion. Completely still grey reads as an empty screen rather than a loading one. Brightness drifting slowly is enough.
- Skip it for fast responses. On a screen that arrives inside 0.3 seconds, all anyone sees is grey flashing once and vanishing.
- Hand off to the error state on failure. A skeleton still sitting there after 30 seconds leaves the user unable to tell waiting from broken.
What this changes is perception. The data does not arrive any sooner, so a measurement like largest contentful paint (LCP), the time until the biggest piece of content renders, does not improve because a skeleton was added. Seeing the structure first makes the same two seconds feel shorter and keeps the layout steadier; that is the whole return.
How it sounds at work
"Let's use a skeleton here instead of a spinner. The list height keeps changing."
Stop the page from jumping as content fills in.
"The skeleton is shorter than the real card. It still shifts when it fills."
The skeleton's size differs from the real screen, so it misses its purpose.
"This one comes back in 0.2 seconds. Let's just render it."
On a fast screen the treatment gets in the way instead.
At Pagedeck, Noa draws the skeleton into the mockup alongside the normal state. Hand over only the normal state and the loading appearance gets improvised during development, which is how skeleton sizes drift away from the real thing. Theo applies one rule from his side: skeletons go on screens where the server response passes 0.5 seconds, and anything faster renders directly.
Easily confused with
| Term | What it does | How it differs |
|---|---|---|
| Optimistic UI | Assumes success and updates before the response arrives | Removes the wait. A skeleton keeps the wait and makes the time look better |
Where to go next
- UI State
see which set this treatment belongs to
- Loading state
the state this treatment expresses
- LCP
the measurement of real speed rather than perceived speed
Last updated 2026-09-16