Text Effects
WordMorph
One word melts into the next. Both words blur and cross-fade under a threshold filter, so their shapes run together like liquid. Best with solid-colour text.
Download
- React
- Tailwind CSS v4
- motion
components/ui/word-morph.tsx
"use client";
import { useEffect, useId, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type WordMorphProps = {
/** Words to cycle through. */
words: string[];
className?: string;
/** Milliseconds each word stays. */
interval?: number;
/** Seconds the melt takes. */
duration?: number;
};
/* WordMorph: one word melts into the next. Both words blur and cross-fade under a threshold filter,
so their shapes run together like liquid. Best with solid-colour text. */
export function WordMorph({ words, className = "", interval = 2400, duration = 0.9 }: WordMorphProps) {
const reduce = useReducedMotion();
const filterId = `wm${useId().replace(/[^a-zA-Z0-9]/g, "")}`;
const [index, setIndex] = useState(0);
const word = words[index % words.length] ?? "";
useEffect(() => {
if (words.length < 2) return;
const id = window.setInterval(() => {
if (!document.hidden) setIndex((i) => (i + 1) % words.length);
}, interval);
return () => window.clearInterval(id);
}, [interval, words.length]);
return (
<span className={`relative inline-grid ${className}`}>
<span className="sr-only">{word}</span>
<svg aria-hidden className="absolute h-0 w-0">
<filter id={filterId}>
{/* Hard alpha cut: soft blurred edges become one solid shape. */}
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 255 -140" />
</filter>
</svg>
<span aria-hidden className="inline-grid" style={{ filter: reduce ? undefined : `url(#${filterId})` }}>
<AnimatePresence initial={false}>
<motion.span
key={index}
className="whitespace-pre text-center [grid-area:1/1]"
initial={{ opacity: 0, filter: "blur(8px)" }}
animate={{ opacity: 1, filter: "blur(0px)" }}
exit={{ opacity: 0, filter: "blur(8px)" }}
transition={{ duration: reduce ? 0 : duration, ease: "easeInOut" }}
>
{word}
</motion.span>
</AnimatePresence>
</span>
</span>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/word-morph.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/word-morph-demo.tsx
import { WordMorph } from "@/components/ui/word-morph"; export default function WordMorphExample() { return ( <div className="grid h-full place-items-center bg-[#f4f1ea] p-6"> <p className="text-center text-[clamp(26px,4.5vw,48px)] font-bold tracking-tight text-[#141414]"> Build it <WordMorph words={["faster", "cleaner", "bolder", "together"]} className="text-[#6d28d9]" /> </p> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| words | string[] | — | Words to cycle through. |
| interval | number | 2400 | Milliseconds each word stays. |
| duration | number | 0.9 | Seconds the melt takes. |
| className | string | "" | Type styles for the text. |