Skeleton
A shimmer placeholder that handoffs to real content with a mask wipe or staggered reveal.
API reference
Components
<Skeleton />
A single skeleton placeholder: a recessed bg-muted block with a compositor-only shimmer sweeping across it. Owns its own in-view gate, so an off-screen bone never burns a compositor frame, and the sweep is a literal-transform string, so it stays unambiguously compositor-only. Decorative (aria-hidden): a screen reader hears the real content, not the placeholder, so mark the loaded copy alive and the bones stay silent.
animate?
Run the shimmer when true; hold a steady block when false. This is YOUR gate (typically !loaded); the bone additionally, and independently, holds steady under prefers-reduced-motion and while scrolled off screen, so you never fold those in yourself. Defaults to true.
className?
Merged onto the bone. Size and shape the placeholder here (h-4 w-40, size-9 rounded-full, ...); a bone with children instead sizes itself to them.
style?
Extra inline styles, merged onto the bone.
children?
Rendered invisibly to size the bone to the exact geometry of the real content it stands in for, so the handoff has zero layout shift. Omit and size the bone with className instead.
<SkeletonReveal />
The single-card load handoff. While loading, it shows your skeleton; when loading flips to false, the skeleton and loaded layers - which share the name view-transition name - crossfade, and the old skeleton layer wipes away left-to-right under a --wipe-driven mask at the gentle token's duration. Under reduced motion the wipe degrades: "calm" keeps a plain opacity crossfade at the same perceived length, "off" is instant. The @property --wipe and ::view-transition-* CSS ship with the component, so there is nothing extra to install.
loading
Whether the skeleton is showing (true) or the loaded content has taken over (false). Flip it to run the handoff; you own the load timer.
skeleton
The skeleton placeholder, shown while loading. Build it from Skeleton bones sized to your loaded content so the wipe has no layout shift.
children
The loaded content, shown once loading is false. Give it and the skeleton the same shell so AnimateView morphs shell-to-shell and only their contents crossfade.
name?
The shared view-transition name the skeleton and loaded layers hand off across. Must be unique per independent reveal on the page. Defaults to velocity-skeleton-card.
className?
Merged onto the wrapper around the handoff (size the stage here).
<SkeletonResolveList />
Shares one loading flag (and optional stagger) with the SkeletonResolveRows nested inside it, so you set the load state once rather than on every row. Renders no DOM of its own - wrap it around your own list element (a <ul>, a table body) and keep full control of the markup.
loading
Whether the feed is still loading (true) or has resolved (false). Shared with every SkeletonResolveRow inside, so you set it once.
children
The rows - one SkeletonResolveRow per feed item (or your own markup driven by useSkeletonResolve). Rendered as-is, so you own the list element and its semantics.
stagger?
Per-row delay step, in seconds, shared with every row. Defaults to the theme's base stagger.
<SkeletonResolveRow />
One row of a staggered skeleton resolve. Stacks your content (normal flow, defining the box) under your skeleton overlay (absolute), and crossfades the two on this row's staggered delay so the row resolves in place with zero layout shift. Reads loading/stagger from an enclosing SkeletonResolveList when present; pass loading directly for a standalone row. Renders a <div> wrapper - put it inside your own <li>/row element.
index
This row's position, used to stagger its handoff after the rows above.
content
The real content layer. Sits in normal flow and defines the row's box the whole time (invisible while loading), so nothing reflows at the handoff - give it its own padding and layout.
skeleton
The bones overlay - typically Skeleton bones laid out to mirror content's geometry. Rendered absolutely on top and faded out as the row resolves.
loading?
Whether the feed is loading. Optional inside a SkeletonResolveList (inherited); required for a standalone row.
stagger?
Per-row delay step, in seconds. Falls back to the list's value, then the theme's base stagger.
className?
Merged onto the row wrapper (the positioned container of both layers).
Hooks
useSkeletonSweep
Resolves the shimmer gate and cadence for a skeleton bone. The cadence is derived from the ambient token (a shimmer wants a longer, calmer sweep, so it runs at 2.5x the cycle length), and the gate folds together your active flag, the theme's reduced-motion strategy, and the observed element's viewport presence. Attach ref to the element you want to gate on. Used by Skeleton; call it directly to build a bone with bespoke geometry that still shares the kit's shimmer feel.
useSkeletonSweep(options: UseSkeletonSweepOptions<T>): SkeletonSweepref
Ref to the element whose viewport presence gates the loop - typically the bone itself, or one shared stage wrapper gating a whole card of bones.
active?
Your own gate, e.g. !loaded. The shimmer additionally, and independently, pauses under prefers-reduced-motion and while the observed element is off screen. Defaults to true.
Returns
shimmering
True only when the shimmer should actually run: your active gate AND motion is allowed AND the observed element is on screen. Drive the sweep overlay's animate/transition off this.
sweepTransition
The token-derived infinite tween for the sweep overlay.
useSkeletonResolve
Resolves the crossfade timing and animate targets for one row of a staggered skeleton resolve. Reveal timing reads the ui token; the per-row delay is index * stagger (theme base stagger by default), so the feed fills in top-to-bottom. Both the stagger and the content-layer rise collapse under reduced motion, and the reset (loading back to true) snaps every row together with no delay. Used by SkeletonResolveRow; call it directly to drive a bespoke row layout.
useSkeletonResolve(options: UseSkeletonResolveOptions): SkeletonResolveindex
This row's position, used to stagger its handoff after the rows above.
loading
Whether the feed is still loading (true) or has resolved (false).
stagger?
Per-row delay step, in seconds. Defaults to the theme's base stagger.
Returns
loaded
True once this row has resolved to its loaded content.
motionAllowed
Whether full motion is allowed (false under reduced motion).
transition
Transition for BOTH layers, so the bones fade out exactly as the content fades in. Carries the per-row delay.
content
animate target for the real-content layer: fades in and settles up a few px (no travel under reduced motion).
skeleton
animate target for the bones overlay: fades out as the row resolves.
Featured in
See the skeleton composed into full sections.
