Text Animation
Create dynamic text effects by staggering letters or words with configurable motion and timing, great for hero sections, promotional content, or onboarding screens.
text-animation-preview.jsx
import TextAnimation from "@/components/text-effects/text-animation/text-animation";
const TextAnimationPreview = () => {
return (
<TextAnimation
text="Transform static text into fluid animation"
variant="slideDown"
/>
);
};
export default TextAnimationPreview; Installation
Run the command from your project root directory (the folder that contains package.json).
Terminal / Console
npx mosaicui-cli@latest text-effects/text-animation 1. Copy the component file
Create a new file called text-animation.jsx
in your reusable components folder (for example
/src/components/) and paste the following
code into it.
text-animation.jsx
import { memo, useEffect, useMemo, useRef } from "react";
import styles from "./text-animation.module.css";
const TextAnimationUnit = memo((props) => {
const {
variant,
animationDelayMs,
animationDurationMs,
children
} = props;
const ref = useRef();
useEffect(() => {
const timeoutId = setTimeout(() => {
ref.current?.classList.add(styles["done"]);
}, animationDelayMs + animationDurationMs);
return () => {
clearTimeout(timeoutId);
};
}, [animationDelayMs, animationDurationMs]);
return (
<span
aria-hidden={true}
className={styles[variant]}
ref={ref}
style={{
"--animation-delay": `${animationDelayMs}ms`,
}}
>
{children === " " ? (
<> </>
) : children}
</span>
);
});
const TextAnimation = (props) => {
const {
text,
variant = "fadeIn",
unit = "letter",
stagger = 20,
delay = 0,
className,
style,
...restProps
} = props;
const animationDurationMs = useMemo(() => (
({
letter: 250,
word: 400,
text: 500,
})[unit] ?? 500
), [unit]);
const textUnits = useMemo(() => (
unit === "text" ? (
[text]
) : (
text
.split(" ")
.filter(Boolean)
.map(word => {
if (unit === "word") {
return [word, " "];
} else {
return [
...word
.split("")
.filter(Boolean),
" "
];
}
})
.flat()
)
), [text, unit]);
return (
<span
{...restProps}
className={[
className,
styles["text-animation"],
].join(" ")}
style={{
...style,
"--animation-duration": `${animationDurationMs}ms`
}}
>
{textUnits.map((entry, entryIndex) => (
<TextAnimationUnit
key={`text-${entry}-${entryIndex}`}
animationDelayMs={delay + (stagger * entryIndex)}
animationDurationMs={animationDurationMs}
variant={variant}
>
{entry}
</TextAnimationUnit>
))}
<span
className={styles["sr-only"]}
>
{text}
</span>
</span>
);
};
export default memo(TextAnimation); 2. Copy the CSS module file
In the same folder, create a file called text-animation.module.css and paste the following
CSS.
text-animation.module.css
.text-animation {
--animation-duration: 0;
position: relative;
>span {
display: inline-block;
opacity: 0;
animation-duration: var(--animation-duration);
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
animation-delay: var(--animation-delay);
&.fadeIn {
animation-name: text-fade-keyframes;
}
&.slideUp {
animation-name: text-slide-up-keyframes;
}
&.slideDown {
animation-name: text-slide-down-keyframes;
}
&.slideLeft {
animation-name: text-slide-left-keyframes;
}
&.slideRight {
animation-name: text-slide-right-keyframes;
}
&.zoomIn {
animation-name: text-zoom-in-keyframes;
}
&.zoomOut {
animation-name: text-zoom-out-keyframes;
}
&.blurIn {
animation-name: text-blur-keyframes;
}
&.done {
opacity: 1;
filter: blur(0px);
transform: translate(0px, 0px) scale(1);
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-fade-keyframes {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes text-slide-up-keyframes {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
@keyframes text-slide-down-keyframes {
0% {
opacity: 0;
transform: translateY(-20px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
@keyframes text-slide-left-keyframes {
0% {
opacity: 0;
transform: translateX(20px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
@keyframes text-slide-right-keyframes {
0% {
opacity: 0;
transform: translateX(-20px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
@keyframes text-zoom-in-keyframes {
0% {
opacity: 0;
transform: scale(0.25);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes text-zoom-out-keyframes {
0% {
opacity: 0;
transform: scale(1.5);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes text-blur-keyframes {
0% {
opacity: 0;
filter: blur(20px);
}
100% {
opacity: 1;
filter: blur(0px);
}
} Examples
Discover how supported props transform components to match your needs.
Variants
text-animation
import TextAnimation from "@/components/text-effects/text-animation/text-animation";
import { useState } from "react";
const TextAnimationVariantsPreview = () => {
const variants = [
"fadeIn",
"slideUp",
"slideDown",
"slideLeft",
"slideRight",
"zoomIn",
"zoomOut",
"blurIn",
];
const [variant, setVariant] = useState(variants[0]);
return (
<div>
<div style={controlStyles}>
<label
htmlFor="text-animation-variants-input"
>
Variants:
</label>
<select
id="text-animation-variants-input"
value={variant}
onChange={(e) => setVariant(e.target.value)}
>
{variants.map(variant => (
<option
key={variant}
value={variant}
>
{variant}
</option>
))}
</select>
</div>
<TextAnimation
text="Create beautiful text animations with ease"
variant={variant}
/>
</div>
);
};
const controlStyles = {
textAlign: "center",
marginBottom: "8px",
};
export default TextAnimationVariantsPreview; Unit & Stagger
text-animation
import TextAnimation from "@/components/text-effects/text-animation/text-animation";
import { useState } from "react";
const TextAnimationUnitAndStaggerPreview = () => {
const units = [
"letter",
"word",
"text",
];
const [unit, setUnit] = useState(units[0]);
const stagger = ({
letter: 20,
word: 50,
text: 0,
})[unit];
return (
<div>
<div style={controlStyles}>
<label
htmlFor="text-animation-unit-input"
>
Unit:
</label>
<select
id="text-animation-unit-input"
value={unit}
onChange={(e) => setUnit(e.target.value)}
>
{units.map(unit => (
<option
key={unit}
value={unit}
>
{unit}
</option>
))}
</select>
</div>
<TextAnimation
text="Craft delightful experiences with animated text"
unit={unit}
stagger={stagger}
variant="slideUp"
/>
</div>
);
};
const controlStyles = {
textAlign: "center",
marginBottom: "8px",
};
export default TextAnimationUnitAndStaggerPreview; Delay
text-animation
import TextAnimation from "@/components/text-effects/text-animation/text-animation";
const TextAnimationDelayPreview = () => {
return (
<TextAnimation
text="Good things take time to appear"
variant="slideDown"
delay={5000}
/>
);
};
export default TextAnimationDelayPreview; Props
Configure the component with the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| text | string | Yes | — | The text content to be animated. |
| variant | "fadeIn" "slideUp" "slideDown" "slideLeft" "slideRight" "zoomIn" "zoomOut" "blurIn" | No | "fadeIn" | Animation style applied to the text. |
| unit | "letter" | "word" | "text" | No | "letter" | Determines how the text is split and animated. |
| stagger | number | No | 20 | Time delay between each animated unit (in milliseconds). |
| delay | number | No | 0 | Delay before the animation starts (in milliseconds). |
| className | string | No | — | Additional CSS classes for styling. |
| style | React.CSSProperties | No | — | Inline styles applied to the container. |