Text Emerge
Animates text by gradually turning blurry letters into clear ones, word or letter by letter.
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; Installation
Run the command from your project root directory (the folder that contains package.json).
Terminal / Console
npx mosaicui-cli@latest text-effects/text-emerge-animation 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,
className,
style,
...restProps
} = 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]);
useEffect(() => {
setCurrentIndex(-1);
}, [text, type]);
return (
<span
{...restProps}
className={[
className,
styles["text-emerge-animation"]
].join(" ")}
style={{
...style,
"--animation-duration": `${animationDurationMs}ms`
}}
>
{textMapping.map((entry, entryIndex) => (
entryIndex <= currentIndex ? (
<TextEmergeUnit
key={`text-${entryIndex}`}
animationDurationMs={animationDurationMs}
>
{entry}
</TextEmergeUnit>
) : null
))}
<span
className={styles["sr-only"]}
>
{text}
</span>
</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;
position: relative;
> 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;
}
}
> .sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
animation: none;
}
}
@keyframes text-emerge-keyframes {
0% {
opacity: 0;
filter: blur(20px);
}
100% {
opacity: 1;
filter: blur(0px);
}
} 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. |
| className | string | No | — | Optional class name applied to the root container. |
| style | React.CSSProperties | No | — | Inline styles applied to the root container. |