SplitText (React)

React component wrapper with lifecycle management and viewport detection.

App.tsx
import { SplitText } from 'fetta/react';

Props

PropTypeDefaultDescription
childrenReactElementSingle React element to split
onSplitfunctionCalled after text is split
onResizefunctionCalled on autoSplit re-split
optionsobjectSplit options (type, classes, etc.)
autoSplitbooleanfalseRe-split on container resize
revertOnCompletebooleanfalseRevert after animation completes
inViewboolean | objectfalseEnable viewport detection
onInViewfunctionCalled when entering viewport
onLeaveViewfunctionCalled when leaving viewport
refRefRef to container element

options

OptionTypeDefaultDescription
typestring"chars,words,lines"What to split: "chars", "words", "lines", or combinations
charClassstring"split-char"CSS class for character spans
wordClassstring"split-word"CSS class for word spans
lineClassstring"split-line"CSS class for line spans
maskstringWrap elements in overflow: clip container: "chars", "words", or "lines"
propIndexbooleanfalseAdd CSS custom properties for indices
willChangebooleanfalseAdd 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.

App.tsx
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.

App.tsx
<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

OptionTypeDefaultDescription
amountnumber0How much visible (0-1)
marginstring"0px"Root margin
oncebooleanfalseOnly trigger once

Auto-Revert

Enable revertOnComplete and return an animation from your callback. The original HTML is restored when the animation finishes.

App.tsx
<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.

App.tsx
<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.

App.tsx
<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.

App.tsx
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>
  );
}

On this page