Skeleton
Skeletons are small placeholder blocks that can be used while content is still loading.
Examples
Basic usage
<div class="flow">
<div class="skeleton" style="inline-size: 12rem"></div>
<div class="skeleton" style="inline-size: 9rem"></div>
</div>
Circle
<div class="cluster">
<div class="skeleton is-circle"></div>
<div class="skeleton is-circle" style="--skeleton-size: 3.5rem"></div>
<div class="skeleton is-circle" style="--skeleton-size: 5rem"></div>
</div>
Animated
<div class="skeleton-shimmer flow">
<div class="skeleton" style="inline-size: 14rem; block-size: 1rem"></div>
<div class="skeleton" style="inline-size: 11rem; block-size: 1rem"></div>
<div class="skeleton" style="inline-size: 8rem; block-size: 1rem"></div>
</div>
Multi-line text
<div class="skeleton-shimmer stack is-tight">
<div class="skeleton" style="inline-size: 95%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 88%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 60%; block-size: 0.875rem"></div>
</div>
List item
<div class="skeleton-shimmer stack">
<div class="media">
<div class="skeleton is-circle media-figure"></div>
<div class="media-body stack is-tight">
<div class="skeleton" style="inline-size: 40%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 70%; block-size: 0.75rem"></div>
</div>
</div>
<div class="media">
<div class="skeleton is-circle media-figure"></div>
<div class="media-body stack is-tight">
<div class="skeleton" style="inline-size: 55%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 60%; block-size: 0.75rem"></div>
</div>
</div>
<div class="media">
<div class="skeleton is-circle media-figure"></div>
<div class="media-body stack is-tight">
<div class="skeleton" style="inline-size: 35%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 80%; block-size: 0.75rem"></div>
</div>
</div>
</div>
Card
<div class="card skeleton-shimmer" aria-busy="true" aria-label="Loading">
<div
class="skeleton"
style="block-size: 8rem; border-radius: var(--radius-md) var(--radius-md) 0 0"
></div>
<div class="stack" style="padding: var(--space-md)">
<div class="skeleton" style="inline-size: 60%; block-size: 1.25rem"></div>
<div class="stack is-tight">
<div class="skeleton" style="inline-size: 90%; block-size: 0.875rem"></div>
<div class="skeleton" style="inline-size: 70%; block-size: 0.875rem"></div>
</div>
<div class="cluster">
<div class="skeleton is-circle" style="--skeleton-size: 1.75rem"></div>
<div class="skeleton" style="inline-size: 5rem; block-size: 0.75rem"></div>
</div>
</div>
</div>
Table rows
<div class="table-wrap">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Status</th>
<th>Joined</th>
</tr>
</thead>
<tbody class="skeleton-shimmer" aria-busy="true">
<tr>
<td>
<div
class="skeleton"
style="inline-size: 80%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 60%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 50%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 70%; block-size: 0.75rem"
></div>
</td>
</tr>
<tr>
<td>
<div
class="skeleton"
style="inline-size: 65%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 75%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 40%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 55%; block-size: 0.75rem"
></div>
</td>
</tr>
<tr>
<td>
<div
class="skeleton"
style="inline-size: 90%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 50%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 65%; block-size: 0.75rem"
></div>
</td>
<td>
<div
class="skeleton"
style="inline-size: 45%; block-size: 0.75rem"
></div>
</td>
</tr>
</tbody>
</table>
</div>
Source comments
Skeleton -- placeholder block for content that's still loading.
Multi-line text placeholders compose stacked .skeleton bars in a
.flow or .stack. No .is-text variant is shipped: a single tall
block sized for N lines doesn't actually look like multi-line
text, so consumers stack bars explicitly.
Markup:
<div class="skeleton" style="inline-size: 12rem; block-size: 1rem"></div>
<div class="skeleton is-circle"></div>
<div class="skeleton is-circle" style="--skeleton-size: 3.5rem"></div>
<div class="skeleton-shimmer flow">
<div class="skeleton" style="inline-size: 75%; block-size: 1rem"></div>
<div class="skeleton" style="inline-size: 60%; block-size: 1rem"></div>
</div>
Archetype: Atom (P3). Pure CSS, no JS.
Mode asymmetry: the fill scale flips intensity direction by mode.
In light mode the shimmer band is lighter than the base (bright
sweep). In dark mode it's darker than the base (dark sweep).
Both register as motion; the visual character differs slightly.
The base + highlight pair sits one step apart on the fill scale
in either direction, deliberately tame -- skeletons are
affordance, not theater.
Reduced motion: the keyframes themselves are overridden to a no-op
inside @media (prefers-reduced-motion: reduce). The gradient stays
visible (the static striped look reads as "loading" too); only the
animation pauses.
Accessibility: skeletons are decorative -- they convey "loading"
visually but carry no semantic value. Add aria-hidden="true" on
each skeleton, or wrap the loading region in [aria-busy="true"]
so assistive tech announces the loading state and skips the
placeholders.
Reference
Source: src/css/extras/skeleton.css
Applies to
.skeleton
Classes
| Class | Kind | Description |
.is-circle | variant | Circle placeholder for avatar or icon stand-ins |
Tokens
| Token | Default | Description |
--skeleton-color | var(--color-fill-strong) | Base fill tint for the skeleton block |
--skeleton-highlight | var(--color-fill) | Shimmer band tint for the animated sweep |
--skeleton-radius | var(--radius-sm) | Corner radius for the skeleton block |
--skeleton-size | 2.5rem | Diameter of the circle placeholder |