React Examples
Common animation patterns using the SplitText React component.
Basic Fade In
The onSplit callback fires after the text is split, giving you access to the split elements for animation.
<SplitText
onSplit={({ words }) => {
animate(
words,
{ opacity: [0, 1], y: [20, 0] },
{ delay: stagger(0.05), duration: 0.5 }
);
}}
>
<p>Fade in each word</p>
</SplitText>Character Reveal
Access chars instead of words to animate each character individually. Use shorter delays for smoother character animations.
<SplitText
onSplit={({ chars }) => {
animate(
chars,
{ opacity: [0, 1], scale: [0.5, 1] },
{ delay: stagger(0.02), duration: 0.3 }
);
}}
>
<p>Character by character</p>
</SplitText>Emoji Support
Fetta correctly handles compound emojis like family groups, flags, and skin tone modifiers โ each emoji is treated as a single character.
<SplitText
onSplit={({ chars }) => {
animate(
chars,
{ opacity: [0, 1], scale: [0.5, 1] },
{ delay: stagger(0.05), duration: 0.3 }
);
}}
>
<p>Family: ๐จโ๐ฉโ๐งโ๐ฆ Flag: ๐ฏ๐ต Skin: ๐๐ฝ</p>
</SplitText>Nested Elements
Fetta preserves inline HTML elements like <a>, <em>, and <strong> when splitting text. All attributes (href, class, id, data-*, etc.) are maintained on the split output.
<SplitText
onSplit={({ chars }) => {
animate(
chars,
{ opacity: [0, 1], y: [10, 0] },
{ delay: stagger(0.02), duration: 0.3 }
);
}}
>
<p>
Click <a href="#">this link</a> or see <em>emphasized</em> and <strong>bold</strong> text
</p>
</SplitText>Line by Line
Lines are detected based on actual rendered positions, so this works with any text that wraps naturally. Great for paragraphs and longer content.
<SplitText
onSplit={({ lines }) => {
animate(
lines,
{ opacity: [0, 1], x: [-20, 0] },
{ delay: stagger(0.1), duration: 0.6 }
);
}}
>
<p>
This paragraph animates line by line.
Each line slides in from the left.
</p>
</SplitText>Masked Line Reveal
Use the mask option to wrap elements in a clipping container with overflow: clip. This creates clean reveal animations where content slides into view from outside its bounds.
<SplitText
onSplit={({ lines }) => {
animate(
lines,
{ y: ["100%", "0%"] },
{ delay: stagger(0.1), duration: 0.5 }
);
}}
options={{ type: "lines", mask: "lines" }}
>
<p>Each line reveals from below with a clipping mask.</p>
</SplitText>Responsive Split
Enable autoSplit to automatically re-split text when the container resizes. Lines are recalculated to match the new width.
If you want to re-trigger animations on resize, use the onResize callback.
<SplitText
autoSplit
onSplit={({ lines }) => {
animate(
lines,
{ opacity: [0, 1], y: [12, 0] },
{ delay: stagger(0.08), duration: 0.4 }
);
}}
>
<p>This text reflows naturally at any width, with lines recalculated on resize.</p>
</SplitText>With Auto-Revert
Set revertOnComplete to automatically restore the original HTML when the animation finishes. Return your animation from the onSplit callback - Fetta handles extracting the .finished promise.
<SplitText
onSplit={({ chars }) =>
animate(chars, { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.02), duration: 0.3 })
}
revertOnComplete
>
<p>Auto-revert after animation</p>
</SplitText>Scroll-Triggered
Use inView to detect when the element enters the viewport, and onInView to trigger the animation. Set elements to invisible in onSplit so they're hidden until the scroll trigger fires. Use onLeaveView to reset styles when scrolling away.
<SplitText
onSplit={({ words }) => {
words.forEach(w => {
w.style.opacity = '0';
w.style.transform = 'translateY(30px)';
});
}}
inView={{ amount: 0.5 }}
onInView={({ words }) =>
animate(words, { opacity: [0, 1], y: [30, 0] }, { delay: stagger(0.03) })
}
onLeaveView={({ words }) => {
words.forEach(w => {
w.style.opacity = '0';
w.style.transform = 'translateY(30px)';
});
}}
>
<p>Reveals on scroll</p>
</SplitText>Scroll-Driven
Link animations to scroll progress using Motion's scroll() function. The animation plays as the target element moves through the viewport.
import { scroll, animate } from 'motion';
const targetRef = useRef<HTMLDivElement>(null);
<div ref={targetRef}>
<SplitText
onSplit={({ words }) => {
const target = targetRef.current;
if (!target) return;
const animation = animate(
words.map((word, i) => [
word,
{ opacity: [0, 1], y: [20, 0] },
{ duration: 0.5, at: i * 0.1, ease: 'linear' }
])
);
scroll(animation, { target, offset: ['start 85%', 'start 20%'] });
}}
options={{ type: 'words' }}
>
<p>Each word reveals as you scroll</p>
</SplitText>
</div>