Skip to content
Component installs need the registry setup
VegaStack Design

Skeleton

A pulsing placeholder that holds the shape of content while it loads — one div, sized and shaped by utility classes.

Status
stable
Since
0.1.0
Accessibility pattern
decorative placeholder

Last updated

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Skeleton before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify skeleton

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/skeleton

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

Usage

import { Skeleton } from "@/components/ui/skeleton";

<Skeleton className="h-[20px] w-[100px] rounded-full" />;

Anatomy

Skeleton — data-slot="skeleton"
SkeletonText — data-slot="skeleton-text"

Examples

Avatar

Give the media block the avatar's own size and radius so the layout does not jump when the image arrives.

Card

Skeletons sit inside the real container. Keep the card, the header and the content; replace only what is still loading.

Text

Stack line skeletons at the line height of the copy they stand in for, and cut the last one short — a full-width final line reads as a block, not as text.

Form

Mirror the label/control rhythm of the form so the fields land where the skeleton put them.

Table

Repeat one row shape for as many rows as the first page will hold. Keep the count fixed — a skeleton whose row count moves is a second layout shift.

RTL

The placeholder is direction-agnostic; sizing comes from the utilities you pass, and the surrounding flex or grid flips with the document.

Lines of text

SkeletonText sketches a few lines while text loads: text — 16px bars of varied widths 12px apart — or rows — full-width 20px bars 8px apart, for a list. It is hidden from assistive technology; name the loading region around it.

<div role="status" aria-label="Loading">
  <SkeletonText lines={3} />
</div>

API Reference

Skeleton adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Accessibility

  • A skeleton is decorative: it carries no role, no text and no accessible name, so screen readers skip it rather than announcing a wall of empty boxes.
  • Announce the loading state once, on the region that owns it — aria-busy="true" on the container, or a live region for a longer wait — rather than on each placeholder.
  • The pulse is animate-pulse, and the global reduced-motion reset in base.css stops it for anyone who asks for less motion. The placeholder stays visible.
  • The fill is bg-muted, which is a surface rather than text, so it carries no contrast obligation of its own.
ContractStates tested
Behaviourdefault, loading
Accessibilitybusy
Visualdefault, loading

Do / Don't

Do
Size each skeleton to the element it replaces, so the real content lands without a layout shift.
Don't
Render skeletons for an empty result — an empty state explains what happened; a skeleton implies data is coming.

Deviations

Upstream's file plus packages/ui/upstream/patches/skeleton.patch. Every hunk:

  • DOC-2 — cn is imported from @vegastack/design. Nothing else differs: the element, the data-slot and the animate-pulse rounded-md bg-muted recipe are upstream's.

On this page