Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Staggered Text Reveal

Display text whose words rise in when they scroll into view, staggered one motion-enter-up step apart.

Status
stable
Since
0.1.0
Accessibility pattern
text-preserving spans

Last updated

Ship agentic UI, fast.

Install

Add Staggered Text Reveal from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/staggered-text-reveal

Usage

import { StaggeredTextReveal } from "@/components/ui/staggered-text-reveal";

<h1>
  <StaggeredTextReveal text="Ship agentic UI, fast." />
</h1>;

Compose it inside a heading — it renders a <span>, never a heading element itself, so it never changes your document's semantic structure.

Examples

Ship agentic UI, fast.

Slower stagger

stepMultiplier scales the per-word delay as a multiple of the --duration-fast motion token (never a raw millisecond value).

Slower per-word stagger

Visibility gate

The reveal waits for the text to scroll into view and then plays once. A reveal below the fold that starts on mount has already finished by the time anyone reaches it, so the reader sees static text and the animation was pure cost. Pass whenVisible={false} for text that is definitely above the fold and should play immediately.

The gate only ever REMOVES the reveal: the server-rendered markup animates, and the client pulls off-screen words back to the FROM state before the first paint. A page whose JavaScript never runs therefore still shows its text.

How it works

The ANIMATION is CSS — there is no JS animation driver; the only JavaScript is the visibility gate, a single one-shot IntersectionObserver that decides WHEN the CSS starts. Each word is an inline-block span carrying the shared motion-enter-up utility with a per-word animation-delay derived from --duration-fast via calc(), and animation-fill-mode: backwards so a not-yet-started word sits at the animation's FROM state instead of flashing visible-then-hidden-then-in. The delay is purely word index × stepMultiplier × --duration-fast — no randomness, no measured layout — so the same text always produces the same timeline (deterministic, VRT-stable once animations settle).

Reduced motion

The global prefers-reduced-motion: reduce reset collapses motion-enter-up's duration to ~0. This component additionally zeros the delay itself — without that, words would still visibly stagger in over real time (just with an instant pop each), which is not the static end state reduced motion requires.

API Reference

PropTypeDefaultDescription
text*stringText to reveal, split on whitespace into individually-staggered words.
stepMultipliernumber1Per-word delay step, expressed as a MULTIPLE of the --duration-fast motion token (never a raw ms value) — word i starts its motion-enter-up animation at i * stepMultiplier * --duration-fast.
whenVisiblebooleantrueHold the words at the animation's FROM state until the element first scrolls into view, then play the reveal once. On by default, because the alternative is worse in the common case: a reveal below the fold that starts on mount has already finished by the time anyone scrolls to it, so the reader sees static text and the animation was pure cost. Pass false for text that is definitely above the fold (a hero) and should play immediately, or when the component renders inside a scroll container an IntersectionObserver cannot observe usefully. The gate is one-shot: once revealed, scrolling away and back does not replay it.

Data attributes and CSS variables on StaggeredTextReveal

AttributeValues
data-revealed""
data-slot"staggered-text-reveal" | "staggered-text-reveal-word"
--stagger-iCSS custom property
--stagger-stepCSS custom property

Accessibility

  • The component renders only nested <span> elements and preserves the semantics of its parent heading.
  • Reading order and accessible text remain the original sentence; visual word wrappers do not split the spoken phrase.
  • Reduced-motion mode removes both the entrance duration and its stagger delay, leaving the static final state.
ContractStates tested
Behaviourdefault
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault

Do / Don't

Do
Compose it inside a real heading element for a hero headline.
Don't
Use it for body copy or anywhere the staggered entrance would repeat on every re-render/scroll.

On this page