{"slug":"loading-skeleton","meta":{"title":"Loading Skeleton","slug":"loading-skeleton","kind":"pattern","summary":"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.","problem":"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.","family":["recover","orient"],"data_shape":["collection","record"],"principles":["orientation","progressive-disclosure"],"interaction":["feedback"],"density":"low","complexity":"low","status":"stable","visibility":"public","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."],"alternatives":[{"slug":"empty-state","when":"There is genuinely nothing to load, which must never look like loading."},{"slug":"optimistic-update","when":"The wait follows a user action and the outcome is predictable."}],"ask_leo":"Replace the loading spinner on this screen with skeleton placeholders.\n\n- Draw the skeleton in the same layout, at the same sizes and positions, as the\n  real content. When the data lands, nothing should move.\n- Show it only after a short delay — around 200 to 300ms — so a fast response\n  never produces a flash of placeholder.\n- Use neutral grey blocks with a gentle shimmer. Do not use fake text, fake\n  numbers or a spinner on top.\n- Match the number of skeleton rows to a typical page, not to the maximum.\n- Mark the region as busy for assistive technology, and announce when the\n  content has arrived.\n- Respect the reduced-motion preference by dropping the shimmer and showing\n  static blocks.\n- If the request fails, replace the skeleton with a real error and a retry.\n  A skeleton that never resolves is the worst outcome — it promises content\n  forever.\n","related":[{"title":"Empty State","url":"/patterns/empty-state","summary":"What to show when the content arrives and there is none — never the same thing."}]},"body":"## Anatomy\n\n```\n  loading                        loaded\n  ┌──────────────────────┐       ┌──────────────────────┐\n  │ ▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓     │       │ Riverside Fit-Out    │\n  │ ▓▓▓▓▓▓  ▓▓▓▓▓▓▓  ▓▓▓ │  ───▶ │ Dana · $8,400 · 12d  │\n  │ ▓▓▓▓▓▓▓▓▓  ▓▓▓▓      │       │ Kestrel Joinery      │\n  └──────────────────────┘       └──────────────────────┘\n     same positions, same sizes — nothing moves on arrival\n```\n\n- **Same geometry as the real thing.** If the layout shifts when content\n  arrives, the skeleton has failed at the one job a spinner cannot do.\n- **A delay before appearing.** Under ~300ms a skeleton is a flicker, which\n  reads as a glitch.\n- **No fake content.** Grey blocks, never placeholder words or numbers — people\n  read them, and briefly believe them.\n- **A real terminal state.** It must resolve to content, an empty state, or an\n  error. Never to itself.\n\n## Why it works\n\nA spinner says *something is happening*. A skeleton says *this specific thing is\nhappening, here, and it will look like this*. That converts an ambiguous wait\ninto an oriented one, and orientation is what makes waiting tolerable.\n\nIt also removes layout shift, which is a real cost rather than a cosmetic one:\ncontent that jumps causes mis-clicks and forces the eye to re-find its place.\n\nThe perceived-speed effect is genuine but secondary. The reason to prefer a\nskeleton is that it is *honest about structure* — it shows the shape of what is\ncoming rather than an abstract indicator of activity.\n\n## The three states it must not confuse\n\nThis is the pattern's real risk. **Loading, empty and broken must look\ndifferent.**\n\n- A skeleton that never resolves reads as loading forever, and people wait\n  instead of reporting.\n- A skeleton replaced by nothing reads as breakage.\n- An [empty state](/patterns/empty-state) shown during loading tells someone\n  their data is gone.\n\nDecide all three renders up front. Most implementations build only the first.\n\n## Getting it wrong\n\n- **Flashing** on fast responses, because there is no delay before showing.\n- **A skeleton that does not match the layout**, so everything jumps on arrival.\n- **Fake text or numbers**, briefly read as real.\n- **Skeleton plus spinner**, which is two answers to one question.\n- **Shimmer with no reduced-motion fallback**, which is a genuine accessibility\n  problem for a decorative effect.\n- **No failure path**, leaving a grey page forever when the request errors.\n\n## Exemplars\n\n**Facebook** popularised the pattern, and the reason it stuck is the geometry\nmatch: the skeleton is the post layout, so the feed never jumps.\n\n**LinkedIn and Slack** both delay the skeleton, which is the detail most\nimplementations skip and the one that separates \"feels fast\" from \"feels\nglitchy\".\n\n**GitHub** is a good counter-example of restraint: most navigations are fast\nenough that no placeholder appears at all, which is the correct choice when the\nwait is short.\n\nThe extractable rule: **a skeleton is a promise about layout.** Keep the\npromise — same shapes, same places — or you have added a flicker and a jump to\na screen that was merely slow.\n"}