Staggered Text Reveal
Display text whose words rise in when they scroll into view, staggered one motion-enter-up step apart.
- Status
- 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-revealUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
text* | string | — | Text to reveal, split on whitespace into individually-staggered words. |
stepMultiplier | number | 1 | Per-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. |
whenVisible | boolean | true | Hold 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
| Attribute | Values |
|---|---|
data-revealed | "" |
data-slot | "staggered-text-reveal" | "staggered-text-reveal-word" |
--stagger-i | CSS custom property |
--stagger-step | CSS 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.
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test |
| Visual | default |