Skeleton
A Skeleton is a set of generic placeholder shapes that reserve a page’s layout while its real content is on its way. It renders as plain HTML and CSS with no JavaScript, so it can paint before an app hydrates — softening the jump from an empty page to a populated one.
Quarantine
The skeleton is a small set of generic shapes — a line, a block, and a circle — that stand in for content while it loads. They are intentionally abstract: a skeleton hints at the shape of what’s coming without mimicking it pixel for pixel. Keep placeholders simpler than the real layout; a rough echo reads as “loading”, an exact replica reads as broken.
Anatomy
Section titled “Anatomy”- Block
- Circle
- Line
When to use it
Section titled “When to use it”Use a skeleton when:
- Content takes long enough to load that an empty page would feel broken, and you can predict the rough shape of what’s coming.
- You want to reserve layout space so the page doesn’t jump when content arrives.
- The placeholder can render before the app hydrates — skeletons are plain HTML/CSS and need no JavaScript.
Avoid a skeleton when:
- The wait is very short (a sub-second flash is more distracting than nothing).
- You can’t predict the layout, so the placeholder wouldn’t match what loads.
- A single, small action is loading — a spinner or inline progress indicator is clearer.
- The operation can fail and needs a message — skeletons imply success is imminent; use an error or empty state instead.
Properties
Section titled “Properties”Three base shapes cover most placeholders. Compose them — with the existing layout and utility classes — to mirror the layout that will replace them.
Block
The default shape — image, media, or card placeholder. The caller sets its size, so a block can stand in for anything from a thumbnail to a hero frame.
Line
Text placeholder. Stack a few at varying widths to suggest a paragraph or heading, with the last line shorter than the rest to mimic the natural break.
Circle
Avatar or icon placeholder. Use it when the real content will be a circular image or rounded badge.
Platform considerations
Section titled “Platform considerations”Desktop
Use skeletons to reserve the dominant blocks of the layout — hero media, card grids, sidebar sections. Match the real layout’s structure so nothing reflows when the content arrives.
Tablet
Same composition as desktop, but stack blocks more aggressively as the grid collapses. Keep the same skeleton shapes — only the layout changes.
Mobile
Drop to single-column skeletons. Keep lines short enough to suggest mobile typography without filling the whole viewport.
Motion
Section titled “Motion”The skeleton can animate to reinforce that content is loading. The treatment is still being decided across three candidates, so while the component is in quarantine all three are available for evaluation:
- Static (current default) — no motion. The cheapest and most restrained.
- Pulse — the fill gently breathes opacity. Calm, and implies no direction.
- Shimmer — a highlight sweeps across the fill. The most familiar pattern, but the busiest.
Whichever is chosen, motion is suppressed under prefers-reduced-motion and the skeleton falls back to a static fill.
Best practices
Section titled “Best practices”Suggest the layout — never replicate it.
Do
Match the placeholder to the real layout’s structure (same rough number of lines, same block proportions), keep skeletons simpler than the content they replace, reserve the space the real content will occupy so the page doesn’t reflow, and use one consistent motion treatment across a view.
Don't
Don’t show a skeleton for loads short enough that it only flashes, don’t build a pixel-perfect replica of the content (abstraction is the point), don’t mix several motion treatments on the same page, and don’t use a skeleton to mask an error or empty state — those need their own messaging.
Content guidelines
Section titled “Content guidelines”The skeleton carries no copy of its own — its job is to hint at the layout, not the words. If a region holds a critical message (“Sorry, we couldn’t load this”) it deserves an explicit error or empty state instead of a placeholder pretending success.
Styles
Section titled “Styles”A skeleton is a single element with the tng-skeleton class. On its own it renders a block — the most common placeholder, filling its container’s width. Set its height with --tng-skeleton-size. There is no JavaScript: the markup paints as-is, before any app hydrates.
Shapes
Section titled “Shapes”The line (is-line) is a text placeholder; stack a few to suggest a paragraph. Its width comes from --tng-skeleton-size, with presets is-sm, is-md, and is-lg.
<div class="tng-skeleton is-line"></div><div class="tng-skeleton is-line is-lg"></div><div class="tng-skeleton is-line is-md"></div><div class="tng-skeleton is-line is-sm"></div>The circle (is-circle) is an avatar or icon placeholder. --tng-skeleton-size sets its diameter.
<div class="tng-skeleton is-circle"></div><div class="tng-skeleton is-circle" style="--tng-skeleton-size: 56px"></div>Motion
Section titled “Motion”Skeletons are static by default. While the component is in quarantine, two motion treatments are exposed so the final choice can be compared in context: is-pulse breathes the fill’s opacity, and is-shimmer sweeps a highlight across it. Both collapse to a static fill under prefers-reduced-motion.
<div class="tng-skeleton is-line"></div><div class="tng-skeleton is-line is-pulse"></div><div class="tng-skeleton is-line is-shimmer"></div>Modules
Section titled “Modules”Skeleton card
Section titled “Skeleton card”A card-shaped placeholder is composed from the base shapes and the existing layout and utility classes — there is no dedicated skeleton-card class. Here tng-stack stacks the shapes, with p-md, background-subtle, border-subtle, and radius-2xs giving the card its surface.
<div class="tng-stack align-items-stretch | p-md background-subtle border-subtle radius-2xs"> <div class="tng-skeleton is-pulse" style="--tng-skeleton-size: 140px" ></div> <div class="tng-group"> <div class="tng-skeleton is-circle is-pulse" style="--tng-skeleton-size: 40px" ></div> <div class="tng-stack align-items-stretch" style="flex: 1"> <div class="tng-skeleton is-line is-pulse"></div> <div class="tng-skeleton is-line is-pulse is-sm"></div> </div> </div></div>A skeleton is a decorative loading placeholder — generic shapes that hold a page’s layout while its real content loads. Its accessible story is short: the shapes carry no information, so they stay hidden from assistive technology, while the loading state itself is announced once. Nothing inside a skeleton is interactive, so it never takes focus. Handled this way, it meets WCAG 2.1 AA.
For designers
Section titled “For designers”- Keep the placeholder fill distinguishable from the surface behind it on every supported theme, so the loading state reads for sighted users. Source: WCAG 1.4.3 Contrast (Minimum).
- If the skeleton animates (pulse or shimmer), honour
prefers-reduced-motionand fall back to a static fill — the motion plays automatically and continuously while content loads, so users who opt out of motion must be able to stop it. Source: WCAG 2.2.2 Pause, Stop, Hide. - Use semantic colour tokens rather than fixed values, so the placeholder adapts correctly across themes and surfaces.
For developers
Section titled “For developers”A skeleton’s shapes are decorative stand-ins that carry no information, so hide them from assistive technology with aria-hidden="true" on the placeholder region — otherwise a screen reader announces a run of empty shapes. Announce the loading state once, on the container that will receive the content: set aria-busy="true" there (or expose a single polite live region), and remove it when the real content arrives — announce the state on the container, never once per shape. Nothing inside a skeleton is focusable and it holds no controls, so focus never lands on placeholder content and is never trapped; don’t add tabindex or real controls to a region that is only standing in for content.
<section aria-busy="true"> <div class="tng-stack align-items-stretch" aria-hidden="true"> <div class="tng-skeleton" style="--tng-skeleton-size: 140px;"></div> <div class="tng-skeleton is-line"></div> <div class="tng-skeleton is-line is-sm"></div> </div></section>