Text morph
Also called morphing text or text morphing
A text transition where the letters two words share slide to their new spots while the others fade out or in, so a label changes without a hard cut.
import { useState } from 'react';
import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
const SPRING = { type: 'spring', duration: 0.4, bounce: 0 } as const;
// Key each letter by the letter plus how many times it has appeared,
// so the shared letters in "Deploy" and "Deployed" keep their identity.
function toLetters(label: string) {
const seen: Record<string, number> = {};
return [...label].map((char) => {
seen[char] = (seen[char] ?? 0) + 1;
return { char, key: `${char}-${seen[char]}` };
});
}
export function DeployButton({ deploy }: { deploy: () => Promise<void> }) {
const [label, setLabel] = useState('Deploy');
const reduceMotion = useReducedMotion();
const transition = reduceMotion ? { duration: 0 } : SPRING;
async function onClick() {
setLabel('Deploying');
await deploy();
setLabel('Deployed');
}
return (
<button
type='button'
onClick={onClick}
disabled={label === 'Deploying'}
className='w-36 rounded-full bg-primary py-2.5 text-sm font-medium text-primary-foreground'
>
<span className='sr-only'>{label}</span>
<span aria-hidden className='relative inline-flex'>
<AnimatePresence mode='popLayout' initial={false}>
{toLetters(label).map(({ char, key }) => (
<motion.span
key={key}
layout='position'
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={transition}
>
{char}
</motion.span>
))}
</AnimatePresence>
</span>
</button>
);
}Animate the "Deploy" button label as a text morph when it changes to "Deploying" and "Deployed": render each letter in its own span keyed by the letter and how many times it has appeared so far, slide shared letters to their new positions with a 400ms spring, and fade removed letters out and new ones in. Give the button a fixed width, keep the full label readable by screen readers, and swap instantly when prefers-reduced-motion is set.
Paste into Layerwise or any AI coding tool.
What is Text morph?
When a label changes, a text morph compares the old and new words letter by letter. Letters that appear in both glide to their new positions, letters that are gone fade out, and new ones fade in. Turning "Deploy" into "Deployed" keeps the first six letters and fades in two; turning "Deployed" into "Redeploy" carries the final "e" and "d" across to the front.
It fits labels that change state in place: Save to Saved, Follow to Following, or a plan name switching with a toggle. Because most letters stay, people can tell the button is the same and only its wording changed.
Keep it quick, around 300 to 500ms, since people are waiting for the result. Animate only transform and opacity, give screen readers the whole label rather than the separate letters, and swap the text instantly when someone prefers reduced motion. A fixed button width, or a width that animates with the text, keeps nearby elements from jumping.
Often confused with
Related terms
All termsLayout animation
An animation that smoothly moves and resizes elements when the layout changes, such as list items gliding to new spots after a sort or filter.
Rotating words
A headline where one word keeps changing in place, rolling or fading through a short list while the rest of the sentence stays put.
Number ticker
A number that animates to its new value, either counting through the values in between or rolling each digit into place like an odometer.
Shared element transition
A transition where an element from one view, like a thumbnail, travels and grows into its place in the next view, linking the two screens.
Build it with Layerwise
Describe your app, watch it run in a live preview, and publish it with a database, storage, and sign-in built in.