Text Emerge
Animates text by gradually turning blurry letters into clear ones, word or letter by letter.
1. Copy the component file
Create a new file called text-emerge-animation.jsx in
your reusable components folder (for example
/src/components/) and paste the following code
into it.
text-emerge-animation.jsx
import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import styles from "./text-emerge-animation.module.css";
const TextEmergeUnit = (props) => {
const {
animationDurationMs,
children
} = props;
const ref = useRef();
useEffect(() => {
const timeoutId = setTimeout(() => {
ref.current?.classList.add(styles["visible"]);
}, animationDurationMs);
return () => {
clearTimeout(timeoutId);
};
}, [animationDurationMs]);
return (
<span
aria-hidden={true}
ref={ref}
>
{children}
</span>
)
};
const TextEmergeAnimation = (props) => {
const {
text,
type = "word",
speed = 100
} = props;
const animationDurationMs = 800;
const [currentIndex, setCurrentIndex] = useState(-1);
const textMapping = useMemo(() => (
text
.split(" ")
.filter(Boolean)
.map(word => {
if (type === "word") {
return [word, " "];
} else {
return [
...word
.split("")
.filter(Boolean),
" "
];
}
})
.flat()
), [text, type]);
useLayoutEffect(() => {
const timeout = setTimeout(() => {
const isLast = currentIndex === (textMapping.length - 1);
if (isLast) return;
setCurrentIndex(currentIndex + 1);
}, speed);
return () => {
clearTimeout(timeout);
}
}, [currentIndex, textMapping, speed]);
return (
<span
aria-label={text}
className={styles["text-emerge-animation"]}
style={{
"--animation-duration": `${animationDurationMs}ms`
}}
>
{textMapping.map((entry, entryIndex) => (
entryIndex <= currentIndex ? (
<TextEmergeUnit
key={`text-${entryIndex}`}
animationDurationMs={animationDurationMs}
>
{entry}
</TextEmergeUnit>
) : null
))}
</span>
)
};
export default memo(TextEmergeAnimation); 2. Copy the CSS module file
In the same folder, create a file called text-emerge-animation.module.css and paste the following CSS.
text-emerge-animation.module.css
.text-emerge-animation {
--animation-duration: 0;
> span {
opacity: 0;
filter: blur(20px);
animation: text-emerge-keyframes var(--animation-duration) ease-in-out forwards;
&.visible {
opacity: 1;
filter: blur(0px);
animation: none;
}
}
}
@keyframes text-emerge-keyframes {
0% {
opacity: 0;
filter: blur(20px);
}
100% {
opacity: 1;
filter: blur(0px);
}
} 3. Use the component
Now you can import and use the component anywhere in your project.
text-emerge-animation-preview.jsx
import TextEmergeAnimation from "@/components/text-effects/text-emerge-animation/text-emerge-animation";
const text = (`
A subtle motion to guide your attention. Nothing loud, nothing distracting — just a quiet transition that makes the interface feel alive.
Good animation isn't decoration; it's a gentle cue that helps you understand where you are and what happens next.
`
);
const TextEmergeAnimationPreview = () => {
return (
<div
style={{
width: "calc(100% - 64px)",
height: "calc(100% - 64px)",
}}
>
<h2>
<TextEmergeAnimation
text={text}
speed={50}
/>
</h2>
</div>
);
};
export default TextEmergeAnimationPreview; Props
Configure the component with the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| text | string | Yes | — | The text to display. Can be a word, sentence, or paragraph depending on type. |
| type | "word" | "letter" | No | "word" | Determines the animation unit: "word" animates one word at a time, "letter" animates each letter individually. |
| speed | number | No | 100 | Typing speed in milliseconds per word or letter, depending on the type. |