Skeleton
hc-skeleton is a placeholder you render in place of content while it
loads. Apply .hc-skeleton to any element and give it a size — the
component supplies the surface color, corner radius, and animation.
It is pure CSS with no JavaScript: you swap the skeleton for the real
markup yourself (an htmx swap, a framework re-render, etc.).
The surface uses the theme-adaptive muted background, so skeletons read correctly in both light and dark mode with no extra work.
Basic usage
Section titled “Basic usage”Size the block from the consumer side — inline style, a utility
class, or a wrapping layout. A skeleton has no intrinsic size.
<div class="hc-skeleton" style="block-size:1rem;inline-size:80%"></div>Shapes
Section titled “Shapes”data-shape accepts rect (default), text, and circle.
- rect — a generic block with the medium corner radius. Use it for cards, images, and thumbnails (size it yourself).
- text — a single text line:
1emtall with a tighter radius. Stack several with decreasing widths to mock a paragraph. - circle — fully rounded with
aspect-ratio: 1. Set one dimension (e.g.inline-size) and it stays square — for avatar / icon slots.
<!-- avatar + two-line caption placeholder --><div style="display:flex;align-items:center;gap:1rem;"> <div class="hc-skeleton" data-shape="circle" style="inline-size:3rem"></div> <div style="display:flex;flex-direction:column;gap:.4rem;flex:1;"> <div class="hc-skeleton" data-shape="text" style="inline-size:40%"></div> <div class="hc-skeleton" data-shape="text" style="inline-size:80%"></div> </div></div>Animation
Section titled “Animation”data-animation accepts pulse (default), wave, and none.
- pulse — the whole block fades in and out.
- wave — a lighter highlight band sweeps across the block. The
highlight is derived from the base color via
color-mix(), so it tracks the active theme automatically. - none — a static block. Motion stays off regardless of the OS setting — handy when a page is dense with skeletons.
<div class="hc-skeleton" data-animation="wave" style="block-size:1.25rem"></div>Both pulse and wave collapse to a static block under
prefers-reduced-motion: reduce, so motion-sensitive users see a flat
placeholder.
htmx usage
Section titled “htmx usage”A skeleton is just markup. Render it in the initial response, then let htmx replace it once the real fragment arrives:
<div data-hx-get="/dashboard/stats" data-hx-trigger="load" data-hx-swap="outerHTML" role="status" aria-busy="true" aria-label="Loading statistics"> <div class="hc-skeleton" data-shape="text" style="inline-size:40%"></div> <div class="hc-skeleton" style="block-size:6rem;margin-block-start:.5rem"></div></div>The server returns the finished <div> (without aria-busy); the
swap removes the skeletons along with the loading state.
Accessibility
Section titled “Accessibility”- Skeletons are decorative. Don’t annotate each block. Instead mark
the loading region with
role="status",aria-busy="true", and an accessible name (aria-label="Loading…", or visually-hidden text if you ship a utility for it). Screen readers then announce the loading state once, not once per placeholder. - Remove
aria-busy(or swap the whole region) when the real content arrives so assistive tech knows loading finished. - Both animations honour
prefers-reduced-motion: reduce.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json):
| Token path | Purpose |
|---|---|
skeleton.bg | Base surface — var(--hc-color-muted-bg), adapts light / dark. |
skeleton.highlight | Wave sweep color, derived from bg via color-mix(). |
skeleton.radius | Corner radius for the rect shape. |
skeleton.text-radius | Corner radius for the text shape. |
skeleton.text-height | Line height for the text shape (1em). |
skeleton.pulse-duration | Pulse cycle duration. |
skeleton.wave-duration | Wave sweep duration. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-skeleton-bg | -highlight--hc-skeleton-radius | -text-radius | -text-height--hc-skeleton-pulse-duration | -wave-duration