Loading Skeleton | LlamaPress Pattern Catalog
LlamaPress
Feedback & Recovery
Pattern

Loading Skeleton

Grey placeholder shapes in the layout the real content will occupy, so a slow screen shows what is coming instead of a spinner or a blank.

Feedback & Recovery Orient

A screen takes a second and a half to load and shows nothing, or a spinner in the middle of an empty page. The person cannot tell whether it is working, broken, or empty — and when the content lands, the whole layout jumps.

Live demo — try it Open full screen
Use when
  • Loading reliably takes longer than about 300ms and less than a few seconds.
  • The shape of the incoming content is known before it arrives.
  • The content is the main point of the screen, not a small part of it.
Avoid when
  • Loading is usually under ~300ms. A skeleton that flashes is worse than nothing.
  • The wait is long or unbounded — that needs progress and an explanation, not a placeholder.
  • You do not know the shape, so the skeleton would lie about what is coming.
  • The result may be EMPTY. A skeleton promises content; deliver an empty state instead of pretending.

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.

Instead of this, consider

Same problem, different trade-off. Pick on the condition, not on taste.

Empty State
When: There is genuinely nothing to load, which must never look like loading.
Optimistic Update
When: The wait follows a user action and the outcome is predictable.

Ask Leo for this

Paste this into your Leo chat. It describes the behaviour, not the code, so Leo can fit it to your data.

Prompt
Replace the loading spinner on this screen with skeleton placeholders.

- Draw the skeleton in the same layout, at the same sizes and positions, as the
  real content. When the data lands, nothing should move.
- Show it only after a short delay — around 200 to 300ms — so a fast response
  never produces a flash of placeholder.
- Use neutral grey blocks with a gentle shimmer. Do not use fake text, fake
  numbers or a spinner on top.
- Match the number of skeleton rows to a typical page, not to the maximum.
- Mark the region as busy for assistive technology, and announce when the
  content has arrived.
- Respect the reduced-motion preference by dropping the shimmer and showing
  static blocks.
- If the request fails, replace the skeleton with a real error and a retry.
  A skeleton that never resolves is the worst outcome — it promises content
  forever.

Related

Pattern
Empty State
What to show when the content arrives and there is none — never the same thing.