SplitText (React)
React component wrapper with lifecycle management and viewport detection.
import { SplitText } from 'fetta/react';Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactElement | — | Single React element to split |
onSplit | function | — | Called after text is split |
onResize | function | — | Called on autoSplit re-split |
options | object | — | Split options (type, classes, etc.) |
autoSplit | boolean | false | Re-split on container resize |
revertOnComplete | boolean | false | Revert after animation completes |
inView | boolean | object | false | Enable viewport detection |
onInView | function | — | Called when entering viewport |
onLeaveView | function | — | Called when leaving viewport |
ref | Ref | — | Ref to container element |
options
| Option | Type | Default | Description |
|---|---|---|---|
type | string | "chars,words,lines" | What to split: "chars", "words", "lines", or combinations |
charClass | string | "split-char" | CSS class for character spans |
wordClass | string | "split-word" | CSS class for word spans |
lineClass | string | "split-line" | CSS class for line spans |
mask | string | — | Wrap elements in overflow: clip container: "chars", "words", or "lines" |
propIndex | boolean | false | Add CSS custom properties for indices |
willChange | boolean | false | Add will-change: transform, opacity hint |
Callback Signature
All callbacks receive the same result object:
interface SplitTextElements {
chars: HTMLSpanElement[];
words: HTMLSpanElement[];
lines: HTMLSpanElement[];
revert: () => void;
}Basic Example
Wrap any element with SplitText and use the onSplit callback to animate.
import { SplitText } from 'fetta/react';
import { animate, stagger } from 'motion';
<SplitText
onSplit={({ words }) => {
animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
}}
>
<h1>Animated Text</h1>
</SplitText>Viewport Detection
Use the inView prop for scroll-triggered animations. Set initial styles in onSplit, then animate in onInView.
<SplitText
onSplit={({ words }) => {
words.forEach(w => w.style.opacity = '0');
}}
inView={{ amount: 0.5, once: true }}
onInView={({ words }) =>
animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) })
}
>
<p>Animates when scrolled into view</p>
</SplitText>inView Options
| Option | Type | Default | Description |
|---|---|---|---|
amount | number | 0 | How much visible (0-1) |
margin | string | "0px" | Root margin |
once | boolean | false | Only trigger once |
Auto-Revert
Enable revertOnComplete and return an animation from your callback. The original HTML is restored when the animation finishes.
<SplitText
revertOnComplete
onSplit={({ words }) => {
return animate(words, { opacity: [0, 1] });
}}
>
<h1>Reverts after animation</h1>
</SplitText>Masked Reveal
Use mask in options to wrap elements in a clipping container. Content slides into view from outside its bounds.
<SplitText
options={{ type: 'lines', mask: 'lines' }}
onSplit={({ lines }) => {
animate(lines, { y: ['100%', '0%'] }, { delay: stagger(0.1) });
}}
>
<p>Each line reveals from below</p>
</SplitText>Nested HTML Elements
Fetta preserves inline elements like <a>, <em>, <strong> when splitting. All attributes (href, class, data-*, etc.) are maintained.
<SplitText
onSplit={({ chars }) => {
animate(chars, { opacity: [0, 1] }, { delay: stagger(0.02) });
}}
>
<p>Click <a href="/signup">here</a> to <em>get started</em></p>
</SplitText>With Ref (Scroll-Driven)
Use a ref to link scroll-driven animations with Motion's scroll() function.
import { useRef } from 'react';
import { SplitText } from 'fetta/react';
import { animate, scroll, stagger } from 'motion';
function ScrollAnimation() {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div className="h-[200vh]" ref={containerRef}>
<SplitText
onSplit={({ words }) => {
const animation = animate(
words,
{ opacity: [0, 1], y: [50, 0] },
{ delay: stagger(0.05) }
);
scroll(animation, {
target: containerRef.current!,
offset: ["start 85%", "start 15%"]
});
}}
>
<p>Scroll to reveal</p>
</SplitText>
</div>
);
}