Skip to main content

Overview

The React <SplitText> component wraps the core splitText() function with React lifecycle management, automatic cleanup, and viewport-based animation triggers.
import { SplitText } from "griffo/react";
Bundle size: 8.23 kB (minified + brotli)

Basic Usage

import { SplitText } from "griffo/react";
import { animate, stagger } from "motion";

<SplitText
  options={{ type: "words" }}
  onSplit={({ words }) => {
    animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
  }}
>
  <h1>Hello World</h1>
</SplitText>;
<SplitText> requires exactly one child element. The child’s text content will be split.

Lifecycle Callbacks

onSplit

Called immediately after text is split:
<SplitText
  options={{ type: "chars" }}
  onSplit={({ chars, words, lines, revert }) => {
    console.log(`Split ${chars.length} characters`);

    // Return animation for revertOnComplete support
    return animate(chars, { opacity: [0, 1] });
  }}
>
  <p>Animated text</p>
</SplitText>
onSplit
(result: SplitTextElements) => AnimationCallbackReturn
Receives { chars, words, lines, revert }. Return animation for auto-revert.

onResplit

Called when autoSplit triggers a re-split:
<SplitText
  options={{ type: "words,lines" }}
  autoSplit
  onResplit={({ words, lines }) => {
    console.log(`Re-split into ${lines.length} lines`);
    animate(words, { opacity: [0, 1] });
  }}
>
  <p>Responsive text</p>
</SplitText>

onRevert

Called when text is reverted (manual or automatic):
<SplitText
  onSplit={({ words }) => animate(words, { opacity: [0, 1] })}
  revertOnComplete
  onRevert={() => {
    console.log("Text reverted to original HTML");
  }}
>
  <h2>Temporary split</h2>
</SplitText>

Viewport Intersection

Trigger animations when elements enter/leave viewport:

onViewportEnter

<SplitText
  options={{ type: "words" }}
  viewport={{ amount: 0.5 }}
  onViewportEnter={({ words }) => {
    return animate(words, { opacity: [0, 1] }, { delay: stagger(0.05) });
  }}
>
  <p>Animates when scrolled into view</p>
</SplitText>

onViewportLeave

<SplitText
  options={{ type: "chars" }}
  viewport={{ amount: 0.3 }}
  onViewportEnter={({ chars }) => animate(chars, { opacity: 1, scale: 1 })}
  onViewportLeave={({ chars }) => animate(chars, { opacity: 0, scale: 0.8 })}
>
  <h3>Fades out when leaving viewport</h3>
</SplitText>

Viewport Options

<SplitText
  viewport={{
    once: true, // Only trigger once
    amount: 0.5, // 50% visibility required
    margin: "0px 0px -100px 0px", // IntersectionObserver rootMargin
  }}
  onViewportEnter={({ words }) => animate(words, { opacity: [0, 1] })}
>
  <p>Scroll-triggered animation</p>
</SplitText>
viewport.once
boolean
default:false
Only trigger onViewportEnter once
viewport.amount
number | 'some' | 'all'
default:0
How much of the element must be visible
viewport.leave
number | 'some' | 'all'
default:0
How much visibility is required to consider out of view
viewport.margin
string
default:"0px"
IntersectionObserver rootMargin

Initial Styles

Apply styles immediately after split:
<SplitText
  options={{ type: "words" }}
  initialStyles={{
    words: { opacity: 0, transform: "translateY(20px)" },
  }}
  onViewportEnter={({ words }) => animate(words, { opacity: 1, y: 0 })}
>
  <h2>Pre-styled words</h2>
</SplitText>

Function-based Styles

<SplitText
  options={{ type: "chars" }}
  initialStyles={{
    chars: (el, index) => ({
      opacity: 0,
      transform: `translateY(${20 + index * 2}px)`,
    }),
  }}
>
  <p>Staggered initial positions</p>
</SplitText>

Reset on Viewport Leave

Re-apply initial styles when leaving viewport:
<SplitText
  options={{ type: "words" }}
  initialStyles={{
    words: { opacity: 0, transform: "translateY(20px)" },
  }}
  viewport={{ amount: 0.5 }}
  onViewportEnter={({ words }) =>
    animate(words, { opacity: 1, y: 0 }, { delay: stagger(0.05) })
  }
  resetOnViewportLeave // Re-applies initialStyles
>
  <p>Re-animates each time you scroll into view</p>
</SplitText>
resetOnViewportLeave is perfect for scroll-triggered animations that should reset when scrolling away.

Auto-revert on Complete

<SplitText
  options={{ type: "words" }}
  onSplit={({ words }) => animate(words, { opacity: [0, 1] })}
  revertOnComplete // Reverts when animation finishes
>
  <h1>Temporary split</h1>
</SplitText>
revertOnComplete requires onSplit or onViewportEnter to return an animation (Motion, GSAP, or Promise).

Auto-split on Resize

<SplitText
  options={{ type: "words,lines" }}
  autoSplit
  onResplit={({ lines }) => {
    console.log(`Re-split into ${lines.length} lines`);
  }}
>
  <p>Responsive text that re-splits on resize</p>
</SplitText>

Font Loading

By default, <SplitText> waits for document.fonts.ready:
// Default behavior (recommended)
<SplitText waitForFonts={true}>
  <h1>Waits for fonts</h1>
</SplitText>

// Disable for immediate split
<SplitText waitForFonts={false}>
  <h1>Splits immediately</h1>
</SplitText>
The component is hidden with visibility: hidden while waiting for fonts.

Wrapper Element

Customize the wrapper element:
<SplitText
  as="section"
  className="my-wrapper"
  style={{ padding: "2rem" }}
  options={{ type: "words" }}
>
  <h1>Custom wrapper</h1>
</SplitText>
Renders:
<section class="my-wrapper" style="padding: 2rem; visibility: visible;">
  <h1><!-- split text here --></h1>
</section>

Forwarding Refs

import { useRef } from "react";

const MyComponent = () => {
  const wrapperRef = useRef<HTMLDivElement>(null);

  return (
    <SplitText ref={wrapperRef} options={{ type: "words" }}>
      <h1>Ref forwarded to wrapper</h1>
    </SplitText>
  );
};

Complete Example

import { SplitText } from "griffo/react";
import { animate, stagger } from "motion";

export default function AnimatedHeading() {
  return (
    <SplitText
      options={{ type: "chars,words", mask: "words" }}
      initialStyles={{
        words: { overflow: "clip" },
        chars: { transform: "translateY(100%)" },
      }}
      viewport={{ amount: 0.5, once: true }}
      onViewportEnter={({ chars }) =>
        animate(
          chars,
          { y: 0 },
          {
            duration: 0.65,
            delay: stagger(0.02),
          }
        )
      }
      revertOnComplete
    >
      <h1>Scroll to reveal</h1>
    </SplitText>
  );
}

Props Reference

children
ReactElement
required
Single React element containing text to split
options
SplitTextOptions
Core split options (see Vanilla JS Guide)
onSplit
(result: SplitTextElements) => AnimationCallbackReturn
Called after split. Return animation for revertOnComplete.
onResplit
(result: SplitTextElements) => void
Called when autoSplit triggers
onRevert
() => void
Called when text is reverted
onViewportEnter
(result: SplitTextElements) => AnimationCallbackReturn
Called when entering viewport
onViewportLeave
(result: SplitTextElements) => AnimationCallbackReturn
Called when leaving viewport
viewport
ViewportOptions
IntersectionObserver configuration
autoSplit
boolean
default:false
Re-split on container resize
revertOnComplete
boolean
default:false
Auto-revert when animation completes
resetOnViewportLeave
boolean
default:false
Re-apply initialStyles when leaving viewport
initialStyles
InitialStyles
Apply styles after split (static or function-based)
waitForFonts
boolean
default:true
Wait for document.fonts.ready before splitting
as
keyof HTMLElementTagNameMap
default:"div"
Wrapper element type
className
string
Wrapper class name
style
React.CSSProperties
Wrapper styles

Vanilla JS

Framework-agnostic core API

Motion

Declarative animations

Accessibility

Screen reader support

Performance

Optimization tips

Build docs developers (and LLMs) love