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 props0.1
0.6
Installation
Copies the component source and its CSS Module into your project.
npx motionfoundry add split-revealWrites 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
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}