Skip to content
ExamsLibraryFor agentsConnect your agent

Morphs and swaps

intermediate·7 min·Markdown for agents

What this lesson teaches

Change a label, an icon or an image in place so it reads as the same thing changing, not two things trading places.

  1. 1Shared-letter text
  2. 2Icons that morph
  3. 3When you must crossfade, add blur
  4. 4Animated icons: hover by default, a trigger for everything else

Press the button in each mode and watch the first three letters.

Press the button a few times in each mode. Watch the first three letters.

Continue becomes Confirm. In one mode, 'Con' never moves.

In the crossfade, the whole word fades out and a new word fades in. Your brain sees two buttons. In the shared-letter version, "Con" stays put and only the ending changes, so it reads as one button changing its mind: "got it, now confirm". That's the last lesson's rule applied to text. What stays the same should stay still; only the difference moves.

Shared-letter text

This is a Benji Taylor pattern from Family. The recipe: split the label into characters, keep the characters that match at the same position, and animate only the ones that change (a short fade and an 8px slide, about 180ms, with a tiny stagger).

// inside Motion's AnimatePresence, mode="popLayout"
text.split("").map((char, i) => (
  <motion.span key={`${i}-${char}`} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }}>
    {char}
  </motion.span>
));

The key, made of the position and the character, is what keeps shared letters mounted. Good pairs share a start: Save → Saved, Connecting → Connected, 12 → 13. Skip it when nothing is shared (Cancel → Done: crossfade instead), for long sentences, and for translated text, where the shared letters change by language.

Icons that morph

Pick icons one after another and watch the big one.

3 lines, always

Different shapes move their coordinates. The arrows only rotate, because they are the same shape pointing a different way.

Every icon is exactly three lines; spares collapse to an invisible dot.

Benji Taylor built the Claude app's animated icons on one constraint: every icon is three SVG lines. An icon that needs only two collapses its third line to an invisible point at the center. Because every icon has the same shape of data, any icon can into any other by moving coordinates.

Two strategies, depending on the pair:

  • Different shapes (menu → close, plus → check): interpolate each line's coordinates.
  • Same shape, different direction (the four arrows, or plus → × by 45°): just rotate. It's cheaper and reads more clearly as the same object turning.

It works for a small active set (play/pause, expand/collapse, chevrons, a dot → ring → check status), roughly 5 to 20 icons. It's stroke-only, so filled icons and big marketing sets belong to a normal icon pack.

When two stock icons can't share a schema, like copy → check, swap them with a deep scale instead: scale 0.3 → 1, opacity 0 → 1 and blur(3px) → 0, a 0.35s spring with no bounce. A timid 0.8–0.9 scale looks like a glitch.

When you must crossfade, add blur

Some swaps are between things that can't morph: two photos, two illustrations, a sun and a moon. Tap the icon several times in each mode.

opacity only · 350ms

Tap the icon a few times in each mode. The plain one shows two ghosts halfway; the blurred one shows one shape.

Halfway through a plain crossfade you see two ghosts. Blur turns them into one soft shape.

Jakub Antalik's most reused trick: pair the with a small blur, 2px on desktop and 4px on phones, on the same timing.

.swap {
  opacity: 0;
  filter: blur(2px);
  transition: opacity 250ms var(--ease-out), filter 250ms var(--ease-out);
}
.swap[data-state="visible"] { opacity: 1; filter: blur(0); }

At the midpoint of a plain crossfade, both images sit at half opacity, a double exposure your eye reads as two objects. Blur replaces that moment with one out-of-focus shape that resolves into the next. Don't blur text (it reads as broken focus), don't use it on hover, and test anything bigger than ~400px on a slow phone.

Blur is cheap to move but expensive to draw on big surfaces.

Animated icons: hover by default, a trigger for everything else

If you ship animated icons as components, the pattern from lucide-animated.com is worth copying. The icon plays on :hover of its button with zero setup, which is fast, needs no JavaScript and can fire on keyboard focus too. But touch screens have no hover, and some moments aren't hovers at all (a "saved" celebration, an onboarding sequence). So the component also exposes an imperative handle:

const ref = useRef(null);
// the like button: onClick={() => ref.current?.trigger()}
// the heart icon component receives ref={ref} and plays when trigger() is called

Gate the hover rule behind @media (hover: hover) so taps don't leave icons stuck mid-animation. Don't add trigger() to icons that don't animate, and don't wrap a permanently spinning loader in a ref.

Morph instead of swap· for your agent
For the submit button that changes from "Continue" to "Confirm", keep the shared letters ("Con") in place and animate only the changing characters (opacity and 8px translateY, ~180ms) using AnimatePresence mode="popLayout" with keys of index+char. For icon swaps that can't morph (copy → check), use scale 0.3 → 1, opacity, and blur(3px) → 0 with a spring { duration: 0.35, bounce: 0 }. For image crossfades, add filter: blur(2px) (4px on mobile) on the same timing as the opacity. Never blur text.

Quick check

A theme toggle swaps a sun icon for a moon. What's the best transition?

What this lesson builds on and leads to, across every course.

  • Should it move at all?
  • Cards, icons, avatars and APIs
  • Words matter