Skeleton
A skeleton holds the place of content that is still loading, painted with
--muted. It is purely visual: it never takes focus and, unless label is
given, is hidden from assistive technology, so the host (or label) is what
announces the busy state.
import Skeleton from '@zuilib/components/skeleton'
Shapes
rect is one block, circle a disc the size of the md Avatar
(--control-height-md), text a stack of bars each 1em tall so it follows
the surrounding font size. With several lines the last bar is 60% wide so
the block reads as a paragraph.
Composed
Combine shapes to mirror the layout that is loading, so nothing jumps when the content arrives.
Animation
pulse fades the surface in and out; wave sweeps a foreground/8 sheen
across it so it reads on both the light and the dark surface. Both stop
under prefers-reduced-motion.
Announced
label renders visually hidden text and makes the root a role="status"
region. Screen readers announce changes inside a region that is already
mounted, not one inserted together with its text, so keep the region in the
document and toggle its content, or let the host carry aria-busy.
Props
SkeletonProps extends the <div> attributes minus children, with:
| Prop | Type | Default | Description |
|---|---|---|---|
shape | 'rect' | 'circle' | 'text' | 'rect' | |
width | number | string | rect / text w-full; circle --control-height-md | Number is px. A circle given only one of width / height uses it for both |
height | number | string | rect h-4; circle --control-height-md; text 1em per line | Number is px. For text it is the height of each line |
lines | number | 1 | Bars for shape="text"; the last of several is 60% wide |
animate | 'pulse' | 'wave' | 'none' | 'pulse' | |
label | string | — | Visually hidden text; makes the root role="status" instead of aria-hidden |
className | string | — | Merged last onto the root |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="skeleton"] | div | Root. Also data-shape and data-animate |
[data-slot="skeleton-line"] | div | One bar of a text skeleton; aria-hidden |
[data-slot="skeleton-label"] | span | The visually hidden label |
Tokens
| Token | Used for |
|---|---|
--muted | Surface |
--foreground | Wave sheen (at 8% alpha) |
--control-height-md | Circle diameter |
--radius-md, --radius-sm, --radius-full | Rect, text bar and circle corners |
--animate-pulse, --animate-skeleton-wave | Animations |
Accessibility
- Without
labelthe root isaria-hidden; mark the loading host witharia-busyso the state is still conveyed. - With
labelthe root isrole="status"(polite). It is notaria-busy, since a busy live region defers its own content and would hold back the label. - Never focusable and never interactive.
- Both animations stop under
prefers-reduced-motion.