Text morph
Morph a label into its next state, letter by letter.
textmotionstatus
Preview
Text morph preview
Live · keyboard ready
Good interfaces are built on spacing.
When to use
- Status words that change a few letters at a time, such as Follow and Following.
- Custom buttons or badges that need a morphing label without the rest of the button.
- Inline counters or short state labels that should glide instead of jump.
When not to use
- Use word-rotate for a sentence with one cycling word.
- Use button or action-swap when you want the morph plus button behaviour.
- Avoid it for text that wraps, since it always renders a single line.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "text-morph",
"type": "registry:ui",
"title": "Text morph",
"description": "Morph a label into its next state, letter by letter.",
"categories": [
"text"
],
"dependencies": [
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/text-morph/text-morph.tsx",
"type": "registry:component",
"target": "@components/manicat/text-morph/text-morph.tsx"
},
{
"path": "registry/components/text-morph/text-morph.module.css",
"type": "registry:component",
"target": "@components/manicat/text-morph/text-morph.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/text-morph",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/text-morph",
"markdown": "https://uiarc.dev/components/text-morph/markdown",
"tags": [
"text",
"motion",
"status"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { useEffect, useState } from "react";
import { TextMorph } from "@/components/manicat/text-morph/text-morph";
const morphWords = ["spacing", "grid", "rhythm", "order"];
export function TextMorphExample() {
const [index, setIndex] = useState(0);
useEffect(() => {
const id = setInterval(() => setIndex((current) => (current + 1) % morphWords.length), 2400);
return () => clearInterval(id);
}, []);
return (
<p className="w-full text-center text-sm text-(color:--text-muted)">
Good interfaces are built on <TextMorph as="strong">{morphWords[index]}</TextMorph>.
</p>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Rotating word A word inside a sentence that morphs into the next one, letter by letter.
Rotating word
A word inside a sentence that morphs into the next one, letter by letter. preview
Live · keyboard ready
Good interfaces are built on spacing.
rotating-word-a-word-inside-a-sentence-that-morphs-into-the-next-one-letter-by-letter-.tsxtsx
"use client";
import { useEffect, useState } from "react";
import { TextMorph } from "@/components/manicat/text-morph/text-morph";
const words = ["spacing", "grid", "rhythm", "order"];
export function TextMorphRotatingExample() {
const [index, setIndex] = useState(0);
useEffect(() => {
const id = setInterval(() => setIndex((current) => (current + 1) % words.length), 2400);
return () => clearInterval(id);
}, []);
return (
<p className="w-full text-center text-sm text-(color:--text-muted)">
Good interfaces are built on <TextMorph as="strong">{words[index]}</TextMorph>.
</p>
);
}
Accessibility
- The plain label is in a visually hidden span; the animated glyphs are aria-hidden.
- It does not announce changes. Wrap it in your own aria-live region if a status change must be announced.
Motion
- Letters are matched by character and occurrence, so shared letters glide to their new positions on a spring while new letters stagger in with a blur.
- The frame width springs to the new label; font loading or resizes follow immediately without animating.
- Reduced motion swaps the label and width instantly.
Responsive behavior
- The frame width springs to each label, and a ResizeObserver follows font loading and container resizes without animating.
- The label never wraps, so keep it short in narrow layouts.
Performance
- Every character is a motion span with layout animation; keep labels to a few words.
Notes for AI
- Use for status words that change a few letters at a time (Follow / Following, Save / Saved). For a sentence with a cycling word use word-rotate.
- Keep labels to one short line; it never wraps.