# Text morph

> Text that changes the way it should: shared letters slide over, new ones rise out of a blur, the width eases.

Most interfaces swap text in one frame: "Save" is simply replaced by "Saved". Here the letters both words share stay alive and slide to their new places, new letters rise in out of a soft blur one after another, and the ones that leave lift away where they stood. The box eases to its new width, so the button or pill around it resizes in the same motion. Words with little in common crossfade as a whole instead of flinging single letters across. Use it for any label that changes.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/text-morph.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): none
- Docs and live preview: https://hairlineui.com/ui/text-morph
- Registry item (source): https://hairlineui.com/r/text-morph.json

## Usage

```tsx
import { TextMorph } from "@/components/text-morph";

<button>
  <TextMorph>{saved ? "Saved" : "Save"}</TextMorph>
</button>
```

## In a headline

Morph one word inside a sentence; the sentence reflows around it smoothly.

```tsx
"use client";
import { useEffect, useState } from "react";
import { TextMorph } from "@/components/text-morph";

const audiences = ["founders", "designers", "agencies"];

export function Headline() {
  const [i, setI] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setI((n) => (n + 1) % audiences.length), 2200);
    return () => clearInterval(timer);
  }, []);

  return (
    <h1 className="text-5xl font-medium tracking-tight">
      Landing pages for <TextMorph className="text-primary">{audiences[i]}</TextMorph>
    </h1>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` | – | The text. Change it and it morphs. |
| `duration` | `number` | `460` | Morph time in ms. |
| `direction` | `"up" \| "down"` | `"up"` | New letters rise from below ("up") or drop from above ("down"). |
| `animateWidth` | `boolean` | `true` | Ease the width; turn off for text that wraps over several lines. |

## States

- **shared letters**: "Save" → "Saving" keeps "Sav" and glides it; only "ing" is new.
- **new letters**: Rise 0.35em out of a 4px blur, 16ms apart, easing out over 460ms.
- **leaving letters**: Lift the other way and blur out where they stood, in 60% of the time.
- **width**: The box eases from its old width to its new one, carrying its container with it.
- **unrelated words**: With less than half their letters in common, the texts crossfade as a whole.

## What it handles

Most interfaces swap text in a single frame. This is the primitive that makes any label change feel considered, and many of the components in this library are built on it.

- **Shared letters stay**: “Save” to “Saving” keeps “Sav” and glides it into place. Only “ing” is new.
- **New letters rise**: They come up out of a soft blur, one after another.
- **Old letters lift away**: Letters that leave fade where they stood.
- **The box resizes with it**: The width eases, so the button or pill around it resizes in the same motion.
- **Unrelated words crossfade**: When two texts share less than half their letters, they crossfade whole instead of flinging letters about.
- **Real text for readers**: Screen readers and search get the real text, and interrupting a morph continues smoothly from where the letters are.

## Replaces

a text crossfade, letter animation code, button width animation, a split-text library, a screen reader copy

## Where it fits

- **Button labels**: Save becomes Saving becomes Saved, without a hard swap.
- **Hero headlines**: A rotating word in a headline that the sentence reflows around.
- **Status labels**: Pills and badges that change their wording as things progress.
- **Plan names and billing lines**: Any short text that changes when a setting changes.

## Notes

- The real text is in the page for screen readers and search; the moving letters are hidden from assistive tech.
- Words never break mid-word: letters are grouped by word, so lines only wrap at spaces. While the width eases, the words hold to one line, so "Credits left" never wraps halfway into "No credits left".
- Uses the Web Animations API, with no animation library. Interrupting a morph mid-flight continues smoothly from where the letters are.
- With reduced motion the text changes in place.

## In templates

Used in these landing page templates:

- [Bulkhead](https://hairlineui.com/template/bulkhead)
- [Cutroom](https://hairlineui.com/template/cutroom)
- [Emberline](https://hairlineui.com/template/emberline)
- [Encore](https://hairlineui.com/template/encore)
- [Footnote](https://hairlineui.com/template/footnote)
- [Halftone](https://hairlineui.com/template/halftone)
- [Influence](https://hairlineui.com/template/influence)
- [Kept](https://hairlineui.com/template/kept)
- [Parley](https://hairlineui.com/template/parley)
- [Plumb](https://hairlineui.com/template/plumb)
- [Plumb App](https://hairlineui.com/template/plumb-app)
- [Prism](https://hairlineui.com/template/prism)
- [Relay](https://hairlineui.com/template/relay)
- [Shear](https://hairlineui.com/template/shear)
- [Stillform](https://hairlineui.com/template/stillform)
- [Tandem](https://hairlineui.com/template/tandem)
- [Tempo](https://hairlineui.com/template/tempo)
- [Upshot](https://hairlineui.com/template/upshot)

License: MIT (https://hairlineui.com/license#components)
