Motion UIComponentsComponent

Skeleton

A shimmer placeholder that handoffs to real content with a mask wipe or staggered reveal.

npx shadcn@latest add @motion/skeleton

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?

boolean = true

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?

string

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?

CSSProperties

Extra inline styles, merged onto the bone.

children?

ReactNode

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

boolean

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

ReactNode

The skeleton placeholder, shown while loading. Build it from Skeleton bones sized to your loaded content so the wipe has no layout shift.

children

ReactNode

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?

string = "velocity-skeleton-card"

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?

string

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

boolean

Whether the feed is still loading (true) or has resolved (false). Shared with every SkeletonResolveRow inside, so you set it once.

children

ReactNode

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?

number

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

number

This row's position, used to stagger its handoff after the rows above.

content

ReactNode

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

ReactNode

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?

boolean

Whether the feed is loading. Optional inside a SkeletonResolveList (inherited); required for a standalone row.

stagger?

number

Per-row delay step, in seconds. Falls back to the list's value, then the theme's base stagger.

className?

string

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>): SkeletonSweep

ref

RefObject<T | null>

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?

boolean = true

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

boolean

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

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): SkeletonResolve

index

number

This row's position, used to stagger its handoff after the rows above.

loading

boolean

Whether the feed is still loading (true) or has resolved (false).

stagger?

number

Per-row delay step, in seconds. Defaults to the theme's base stagger.

Returns

loaded

boolean

True once this row has resolved to its loaded content.

motionAllowed

boolean

Whether full motion is allowed (false under reduced motion).

transition

ReturnType<typeof useMotionUITransition> & { delay: number }

Transition for BOTH layers, so the bones fade out exactly as the content fades in. Carries the per-row delay.

content

{ opacity: number; transform: string }

animate target for the real-content layer: fades in and settles up a few px (no travel under reduced motion).

skeleton

{ opacity: number }

animate target for the bones overlay: fades out as the row resolves.