Component
Skeleton
Flat placeholder shapes that hold the layout of content that is still loading.
Plain-text source, for agents and clipboard use: /components/source/skeleton.txt
When to use it
Shows the shape of what is coming when its layout is already known: a card, a list, the rows of a table. Each text line occupies exactly the line box of the text it stands in for, so the content replaces it without the page moving. The shapes pulse slowly in a layer fill, with no shimmer and no gradient, and the region says in words what is loading.
Examples
Text
One flux-skeleton--text per line of the text that is coming. The last line of a paragraph runs short, so it reads as prose.
<div aria-hidden="true">
<span class="flux-skeleton flux-skeleton--text"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
</div>Card
The card is the region. The title slot takes flux-card__title, so the line is the height of the heading that replaces it.
<div class="flux-card" aria-busy="true">
<span class="flux-visually-hidden" role="status">Loading departure</span>
<div class="flux-card__title" aria-hidden="true"><span class="flux-skeleton flux-skeleton--text flux-skeleton--short"></span></div>
<div class="flux-card__body" aria-hidden="true">
<span class="flux-skeleton flux-skeleton--text"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
</div>
</div>List with avatars
flux-skeleton-row sets an avatar beside its lines: a name and a line of metadata.
<div aria-busy="true">
<span class="flux-visually-hidden" role="status">Loading guides</span>
<div aria-hidden="true">
<div class="flux-skeleton-row">
<span class="flux-skeleton flux-skeleton--avatar"></span>
<div>
<span class="flux-skeleton flux-skeleton--text flux-skeleton--short"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
</div>
</div>
<div class="flux-skeleton-row">
<span class="flux-skeleton flux-skeleton--avatar"></span>
<div>
<span class="flux-skeleton flux-skeleton--text flux-skeleton--short"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
</div>
</div>
<div class="flux-skeleton-row">
<span class="flux-skeleton flux-skeleton--avatar"></span>
<div>
<span class="flux-skeleton flux-skeleton--text flux-skeleton--short"></span>
<span class="flux-skeleton flux-skeleton--text"></span>
</div>
</div>
</div>
</div>Table rows
The headers are real, because the columns are known; only the body waits. A cell’s line is inline, so it follows the cell’s alignment, including numeric columns.
| Reference | Departure | Status | Pax | Total |
|---|---|---|---|---|
<div class="flux-table-wrap" aria-busy="true">
<span class="flux-visually-hidden" role="status">Loading bookings</span>
<table class="flux-table">
<caption class="flux-visually-hidden">Bookings departing in September</caption>
<thead>
<tr>
<th scope="col">Reference</th>
<th scope="col">Departure</th>
<th scope="col">Status</th>
<th scope="col" data-numeric>Pax</th>
<th scope="col" data-numeric>Total</th>
</tr>
</thead>
<tbody aria-hidden="true">
<tr>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
</tr>
<tr>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
</tr>
<tr>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
<td data-numeric><span class="flux-skeleton flux-skeleton--text"></span></td>
</tr>
</tbody>
</table>
</div>Block
flux-skeleton--block holds the place of a chart, a map or an image. To match what is coming, set --flux-skeleton-block-size on the region to another spacing token.
<div class="flux-card" aria-busy="true">
<span class="flux-visually-hidden" role="status">Loading occupancy chart</span>
<div class="flux-card__title" aria-hidden="true"><span class="flux-skeleton flux-skeleton--text flux-skeleton--short"></span></div>
<span class="flux-skeleton flux-skeleton--block" aria-hidden="true"></span>
</div>Anatomy
| Part | Description |
|---|---|
| Region | The element that will hold the content, with aria-busy="true" until it arrives. |
| Status | A role="status" element naming what is loading, usually flux-visually-hidden. |
| Slot | An aria-hidden element carrying the class of the text it replaces, such as flux-card__title, so the line takes that text’s size and line height. |
| Shape | flux-skeleton with a modifier: a text line, an avatar or a block. |
API
| Name | Kind | Description |
|---|---|---|
flux-skeleton | class | A shape. Always inside an aria-hidden element: the status carries the meaning. |
flux-skeleton--text | class | One line, at cap height in the line box of the surrounding text. The last of several runs short. |
flux-skeleton--short | class | A short line, for a title or a name. |
flux-skeleton--avatar | class | A circle the size of a medium Avatar. |
flux-skeleton--block | class | A rectangle --flux-skeleton-block-size tall, for a chart, map or image. |
flux-skeleton-row | class | An avatar beside a group of lines. |
aria-busy="true" | attribute | On the region, removed when the content replaces the skeleton. |
States
| State | Treatment |
|---|---|
| Loading | The shapes pulse to half strength and back once every --flux-skeleton-duration. |
| Reduced motion | The base layer runs the pulse once and it ends at full strength, so the shapes rest as still fills. |
| Loaded | The content replaces the skeleton in place, and aria-busy is removed. |
Keyboard
| Keys | Action |
|---|---|
— | Not interactive. Nothing inside a skeleton takes focus. |
Accessibility
- Put aria-busy="true" on the region and a role="status" element inside it that names what is loading.
- Mark every slot and shape aria-hidden. Empty headings or cells announced one by one say nothing useful.
- Keep real column headers in a loading table, so the structure is announced and only the data waits.
- The fill is a graphic with no text, so it relies on the status for meaning rather than on its contrast.
Tokens
The tier-3 and tier-2 tokens this component binds to. Change the token, not the component.
- --flux-skeleton-bg | -radius
- --flux-skeleton-block-size | -avatar-size
- --flux-skeleton-duration | -ease | -dim
Do and don’t
Do
- Draw the layout that is actually coming: the same number of columns, a believable number of rows.
- Wait about half a second before showing a skeleton, so a fast response never flashes one.
- Replace the skeleton with a message and a retry action if loading fails.
Don’t
- Do not add a shimmer, a gradient sweep or a glow.
- Do not show a skeleton and a Spinner for the same region.
- Do not skeleton what is already known, such as section headings, field labels and column headers.