skeleton loading

why

loading fails in two directions: a spinner replaces the whole page and the layout convulses when data lands, or invented placeholder content promises a result the response may never deliver.

  • reserving the ready data's exact geometry keeps data arrival a content change, not a layout change — eye and scroll position survive.
  • refusing invented content keeps the pending state truthful, like every other state.

rule

a skeleton is the loading representation of data that will occupy a known position.

  • reserve the ready data's exact visual geometry: container, column, row, image ratio, line count, spacing, action position.
  • when data arrives, content replaces the skeleton; the screen must not become a materially different layout.
  • a skeleton describes absence of data, not an imagined result: never invent names, counts, progress, statuses, avatars, activity, or actions the authoritative response has not supplied.

map data slots, not generic shapes

start from the ready component; name each independently loaded data slot and put the skeleton in precisely that slot. retain already-known layout: page shell, section heading, navigation, static controls, filters, actions whose availability does not depend on the pending result.

ready data shapeloading representationmust stay stable
title textone skeleton line with the title's expected width/heightheading position and surrounding spacing.
metadata linecorresponding short linesrow height and alignment.
fixed-ratio imageblock with the same aspect-ratioimage frame and crop geometry.
fixed row listthe expected initial row count with each row's real gridlist width, row rhythm, and scrollbar behavior.
table or dense gridheader plus matching row/column skeletonscolumn widths and horizontal overflow boundary.
unknown/unbounded resultbounded neutral loading regiondo not manufacture a long list or false count.
  • never skeleton static chrome, or replace the whole page with a centered spinner when only one data region is pending.
  • a spinner is only for a genuine operation without known data geometry (a small action in progress); it is not a substitute for a data skeleton.

state boundary

  • skeleton the first pending read before that data resolves.
  • background refresh with prior authoritative data: keep it visible, optionally add a small truthful refresh affordance; never replace ready content with a skeleton.
  • empty, denied, blocked, and failed are semantic states, not skeleton variants.
staterepresentation
initial pending with known shapeskeleton in the exact data slots.
initial pending with unknown shapebounded loading region or explicit progress only when the operation exposes real progress.
refreshing with prior dataretain prior data; optionally show non-disruptive refresh state.
empty responseowned empty state, never repeated skeleton rows.
failure or denied accessowned recovery/authorization state, never a skeleton.

construction

  1. compose the ready layout first using real representative data dimensions.
  2. identify which dimensions come from pending facts and which are static.
  3. skeleton every pending slot using the ready slot's layout primitives and responsive rules.
  4. keep the same wrapper, grid, gaps, aspect-ratio, and minimum dimensions between pending and ready branches.
  5. keep skeleton motion subtle; respect reduced motion; expose the region's loading state to assistive tech without reading decorative blocks as content.

verification

  1. capture pending and ready states at every supported viewport.
  2. compare outer bounds, column widths, image ratio, row count, line height, scroll position — data arrival must not cause a large layout shift.
  3. throttle the query; confirm only the pending data region changes.
  4. refresh after ready data exists; confirm it stays visible.
  5. exercise empty, error, denied, blocked results — none may render a skeleton implying data is still coming.
  6. test reduced motion and a screen reader; decorative skeleton pieces must not become a noisy list of unlabeled elements.

good and bad

bad

if (isPending) {
return <Spinner className="mx-auto mt-32" />;
}

the whole page changes shape while one known data region loads: data arrival triggers layout convulsion, scroll position and reading focus are lost, and the page geometry is rebuilt twice for no benefit.

good

<WorkflowSummaryCard>
{isPending ? <WorkflowSummarySkeleton /> : <WorkflowSummary value={data} />}
</WorkflowSummaryCard>

the static card and its geometry remain; only the pending data slots are skeleton-reserved. content arrival swaps values into a stable frame with no layout shift, and nothing on screen invents an undelivered result.

search pages

go to any page