Studio KitAdvanced Text Animationsmotion/react

SplitReveal

Masked word-by-word reveal where each word slides up (or down) out of its own clipping line with a staggered expo-out ease.

Preview

Every pixel earns its place.

Customize

4 props
0.1
0.6

Installation

Copies the component source and its CSS Module into your project.

npx motionfoundry add split-reveal

Writes components/ui/split-reveal.tsx and components/styles/split-reveal.module.css.

Usage

Updates live as you change the controls — only props that differ from the defaults are included.

example.tsx
1import { SplitReveal } from "@motionfoundry/ui";2 3export function Example() {4  return (5    <SplitReveal />6  );7}

Props

PropTypeDefault
text

Text revealed word by word from behind a mask.

string'Every pixel earns its place.'
stagger

Delay between consecutive words in seconds.

number0.1
duration

Per-word reveal duration in seconds.

number0.6
direction

Direction words travel in from.

'up' | 'down''up'
className

Additional class merged onto the root element.

string''

Accessibility

The full sentence is provided in a visually hidden span while the masked word spans are aria-hidden. Under prefers-reduced-motion words render in place with no slide.

Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.

Source

components/ui/split-reveal.tsx
1"use client";2 3import * as React from "react";4import { motion, useReducedMotion } from "motion/react";5import styles from "../styles/split-reveal.module.css";6 7export interface SplitRevealProps {8  /** Text revealed word by word from behind a mask */9  text?: string;10  /** Delay between consecutive words in seconds */11  stagger?: number;12  /** Per-word reveal duration in seconds */13  duration?: number;14  /** Direction words travel in from */15  direction?: "up" | "down";16  /** Optional className */17  className?: string;18}19 20export function SplitReveal({21  text = "Every pixel earns its place.",22  stagger = 0.1,23  duration = 0.6,24  direction = "up",25  className = "",26}: SplitRevealProps) {27  const prefersReducedMotion = useReducedMotion();28  const words = text.split(" ").filter(Boolean);29  const offset = direction === "up" ? "110%" : "-110%";30 31  return (32    <span className={`${styles.root} ${className}`.trim()}>33      <span className={styles.srOnly}>{text}</span>34      {words.map((word, i) => (35        <span key={`${word}-${i}`} aria-hidden="true" className={styles.mask}>36          <motion.span37            className={styles.word}38            initial={prefersReducedMotion ? false : { y: offset }}39            animate={{ y: 0 }}40            transition={{ delay: i * stagger, duration, ease: [0.22, 1, 0.36, 1] }}41          >42            {word}43          </motion.span>44        </span>45      ))}46    </span>47  );48}