Skip to content

Skeleton

A decorative placeholder for content that has not loaded yet. Its shape variants (text lines, circle, rectangle, and fill-the-box) combine into any card-like placeholder, with free width, height, and radius overrides and a shimmer animation that goes still under reduced motion.

Import

import { Skeleton } from "@hyzer-labs/ui"

Demo

<Skeleton variant="text" />
<Skeleton variant="circle" />
<Skeleton variant="rect" />
<div style="width: 8rem; height: 4rem;">
	<Skeleton variant="block" />
</div>

Props

NameTypeDefaultNote
variant'text' | 'circle' | 'rect' | 'block''text'Picks a shape and its default dimensions. For a card-like placeholder, compose several Skeletons in your own Stack/Cluster/Card layout; there is no preset prop for it.
widthstring | numbervariant defaultA number is emitted as px; a string is used as written (any CSS length or percentage). This is a per-instance style rather than a theme hook, like Grid's --hz-grid-cols*.
heightstring | numbervariant defaultSee width.
rounded'none' | 'sm' | 'md' | 'lg' | 'full'variant defaultThe shared Rounded scale. circle always forces full, whatever you pass here.
linesnumber1text only. > 1 renders that many stacked bars; a value ≤ 0 clamps to 1 (with a warning in development). Ignored on other variants.
lastLineWidthstring | number'60%'text only, lines > 1. The final line is shortened so the block reads like a real paragraph.
animation'shimmer' | 'pulse' | 'none''shimmer'Every mode collapses to a static muted block under prefers-reduced-motion: reduce.
classstringMerged after the hz-skeleton class.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-skeleton

Data attributes

HookValuesStyles
data-variant'text' | 'circle' | 'rect' | 'block'Default text.
data-animation'shimmer' | 'pulse' | 'none'Default shimmer. Collapses to a static block (same as none) under prefers-reduced-motion: reduce.
data-rounded'none' | 'sm' | 'md' | 'lg' | 'full'1:1 with the --hz-radius-* scale. circle forces full regardless of the rounded prop.

Custom properties

HookValuesStyles
--hz-skeleton-color<color>The base block color. Strengthened in dark (the Badge --hz-badge-tint precedent).
--hz-skeleton-highlight<color>The shimmer sweep color.
--hz-skeleton-speed<time> — default 1.4sThe shimmer/pulse animation cycle.

Part classes

HookValuesStyles
.hz-skeleton-linechild elementOne bar in a multi-line text skeleton (variant="text", lines > 1).

Accessibility

Skeleton stamps aria-hidden="true" by default. It is decorative scaffolding with no role, no live region, and no label, so it cannot announce loading on its own. In the rare case you want it exposed, you can override aria-hidden through the rest props.

That is the deliberate split: Loading announces, Skeleton decorates. Wrap the loading region in aria-busy="true" and pair it with either a labeled indeterminate Loading or a polite aria-live "Loading…" message, so screen-reader users are told content is coming, then told when it arrives. See the paired loading pattern demo below.

Reduced motion works the other way round here from Loading, on purpose. Skeleton's shimmer and pulse go fully still under prefers-reduced-motion: reduce, because the placeholder shape already carries the "content loading here" cue, so no motion is needed. Loading (and Button's loading spinner) keep animating instead, much more slowly, because for them the motion is the cue.

References: MDN: aria-busy · MDN: ARIA live regions