Skip to main content
The <MorphText> component from griffo/morph provides smooth text morphing animations where matching tokens animate between positions, new tokens enter, and removed tokens exit.

Import

import { MorphText } from "griffo/morph";

Basic usage

import { MorphText } from "griffo/morph";
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <MorphText>{count.toString()}</MorphText>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

How it works

<MorphText> uses stable token identity to create smooth morphing animations:
  1. Matching tokens - Tokens that exist in both old and new text animate to their new positions
  2. Entering tokens - New tokens that didn’t exist before play the initialanimate transition
  3. Exiting tokens - Removed tokens play the animateexit transition before unmounting
Tokens are matched based on their content and position, creating natural-looking morphs even when text changes significantly.

Split modes

Split by characters (default)

<MorphText splitBy="chars">
  {text}
</MorphText>
Splits text into individual characters. Best for:
  • Short text changes
  • Character-by-character reveals
  • Scramble effects

Split by words

<MorphText splitBy="words">
  {text}
</MorphText>
Splits text into words. Best for:
  • Longer text
  • Word-based animations
  • Status messages

Animation states

Applied to entering tokens before they animate in:
<MorphText initial={{ opacity: 0, y: -20 }}>
  {text}
</MorphText>
The target state for all visible tokens:
<MorphText animate={{ opacity: 1, y: 0 }}>
  {text}
</MorphText>
Applied to tokens that are being removed:
<MorphText exit={{ opacity: 0, y: 20 }}>
  {text}
</MorphText>

Transition options

<MorphText
  transition={{
    type: "spring",
    bounce: 0.2,
    duration: 0.5
  }}
>
  {text}
</MorphText>

Examples

Loading states

const states = ["Loading", "Loading.", "Loading..", "Loading..."];

<MorphText
  splitBy="chars"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0 }}
>
  {states[index]}
</MorphText>

Status messages

<MorphText
  splitBy="words"
  initial={({ index, count }) => ({
    opacity: 0,
    x: index <= count / 2 ? -75 : 75,
  })}
  animate={{ opacity: 1, x: 0 }}
  exit={({ index, count }) => ({
    opacity: 0,
    x: index <= count / 2 ? 75 : -75,
  })}
>
  {statusText}
</MorphText>

Counter with stagger

import { stagger } from "motion";

<MorphText
  splitBy="chars"
  initial={{ opacity: 0, y: -20 }}
  animate={{ opacity: 1, y: 0 }}
  stagger={0.05}
>
  {count.toString()}
</MorphText>

Completion callback

<MorphText
  onMorphComplete={() => {
    console.log("Morph animation finished");
  }}
>
  {text}
</MorphText>

Props reference

Complete MorphText props documentation

Morph guide

In-depth guide with examples

Build docs developers (and LLMs) love