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
- Line
- Circle
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>For developers
Section titled “For developers”Skeletons are decorative
Section titled “Skeletons are decorative”A skeleton conveys no information on its own — it’s a visual stand-in. Hide it from assistive technology so screen readers don’t announce a string of empty shapes. Mark the placeholder region aria-hidden="true":
<div class="tng-stack align-items-stretch | p-md background-subtle border-subtle radius-2xs" 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>Announce loading once
Section titled “Announce loading once”When you need to tell screen-reader users that content is loading, put a single aria-busy="true" on the region that will receive the content, and let the skeletons inside stay hidden. Announce the state once on the container — never per shape. Remove aria-busy when the real content arrives.
<section aria-busy="true"> <!-- skeletons here, each decorative --></section>Reduced motion
Section titled “Reduced motion”All motion treatments respect prefers-reduced-motion: reduce and fall back to a static fill automatically — no extra markup needed. Don’t reintroduce animation on top of the skeleton in a way that bypasses this.
Don’t trap focus
Section titled “Don’t trap focus”Skeletons are not interactive. Don’t make them focusable or place real controls inside the placeholder while it’s standing in for content — there’s nothing to operate yet.