Skip to main content

What it is

A skeleton is a grey shape that stands where content will be, in roughly the shape of the content, until it arrives. It tells the reader that something is coming and approximately what. It is the loading state for content whose shape you already know — a table’s rows, a card’s lines, a chart’s box. For work whose duration and outcome are unknown, use a Spinner. Nothing about this component is designed — see Open issues for how thoroughly that was checked and why it matters given adoption.

Live example

Exports

Vocabulary

Say “skeleton” for one shape and “skeleton screen” for the assembly of them. Almost every real use is the second: product code builds named compositions — TabLoadingSkeleton, CallListItemSkeleton, FormConfigurationLoadingSkeleton — from many instances of this one component.

Choose Skeleton when

  • The shape of the incoming content is known before it arrives.
  • The wait is long enough to notice and short enough to wait through — roughly a second to a few.
  • The region is large enough that a blank space would read as a failure.

Choose something else when

Anatomy

One element per shape. A skeleton screen is many of them arranged to match the layout they stand in for — same widths, same line count, same spacing as the real content.

Variants, sizes, and states

Four shapes: Three animations: pulse (the default), wave, and false for none. Nothing chooses between them. pulse and wave are both available, the product uses both, and no decision is recorded — TITAN-GAP-31. Until one is chosen, use the default and stay consistent within a screen: two animations in one region read as two different systems loading. Size comes from width and height, or from the content it wraps. There is no size scale, so a skeleton is sized against the thing it replaces — which is the point, and also why a skeleton screen has to be maintained alongside the layout it mirrors.

Edge and failure states

Tokens

No tokens exist for this component. Its colour and animation come from the framework with no decision behind them, and a rounded skeleton’s corner radius does not come from the radius scale. That is the substance of TITAN-GAP-31, and it means a skeleton is currently the one grey in the product that no palette governs.

Composition

A skeleton screen mirrors the layout it replaces. Same number of lines, same widths, same spacing — built with the same spacing-* tokens as the real content, so the two are the same height and nothing moves when the data lands. Only the region that is loading. Page chrome stays: the header, the controls, and the footer are already correct, and greying them out claims they are not. In a table, the rows are the skeleton — column headers stay, because they are known. See Table. Do not animate a skeleton screen’s pieces independently. Many elements pulsing out of phase in one region reads as noise; the framework’s animation is synchronised across instances by default, and staggering it by hand is a decision nobody has asked for. Product code builds these as named components — and three applications ship a TabLoadingSkeleton with 21 instances each, which is the same file three times. If a skeleton screen is worth naming, it is worth sharing; see Loading & skeletons.

Content

A skeleton carries no text. Never put words in one, and never label it “Loading…” — the shape is the message, and a label in a placeholder gets read as content.

Accessibility

A skeleton is not a status message. It is decorative: it should be hidden from assistive technology, and the region should announce its loading state instead — a busy state on the region that is loading, and an announcement when it finishes. A screen-reader user given a list of grey boxes has been told nothing. Announce completion, not the wait. The reader who cannot see the skeleton needs to know when the content is ready, which is the announcement most loading states omit. Reduced motion is unhandled. The animation does not respond to prefers-reduced-motion, and the reader who has asked for less movement gets a pulsing region anyway. Where a screen shows many skeletons at once, pass animation={false} under that preference until the component handles it — TITAN-SKL-05. A skeleton must not be the only indication a load failed. Something that never resolves reads as a slow network, and a reader will wait far longer than they should before concluding otherwise.

Constraints

Known issues

Skeleton: open issues

Divergences, open decisions, and undocumented gaps for Skeleton.

Why it works this way

A skeleton buys patience by being specific. A spinner says “wait”; a skeleton says “wait, and here is what is coming” — which is why matching the real layout matters more than the animation does, and why using one for content of unknown shape gives up the only advantage it has. It is decorative on purpose. The temptation is to make it announce itself, since it is the most visible sign of loading. But the shapes carry no information, and a screen reader reading out a dozen placeholders is noise. The loading state belongs to the region; the skeleton is only how it looks.

Status and changelog

Last modified on September 7, 2026