# Morphing shapes, and when not to

> Author two paths that really morph into each other, recognise the ones that never will, and know when a simple icon swap is the better answer.

Module: SVG and icons · Level: intermediate · 7 min · https://school.agents.org.in/visual/svg/path-morphing

Morph the circle into a square. Then switch to "Mismatched" and try again.

*[Interactive demo: Matched: the square is written with the circle's own command list, so it glides. Mismatched: a plain square, so the browser can only flip halfway.]*

Both squares look identical when they sit still. Only one can be reached from the circle. The difference is not the shape. It is how the shape is written down.

## The one rule

A path morph (One SVG shape smoothly turning into another by sliding every number in its path to a new value. It only works when both paths have the same commands in the same order) is the browser sliding each number in one path to the matching number in the other. So it needs every number to have a partner:

**both paths need the same commands, in the same order, of the same type.**

Four curves can become four curves. Four straight lines can't become four arcs, because there is nothing to pair up. When the lists don't match, the browser gives up and swaps one shape for the other.

> **In plain words.** A path is a recipe: "move to here (M), curve to there (C), close up (Z)". A morph walks two recipes side by side, step by step. If one recipe has a step the other doesn't, the walk can't happen.

## Writing two shapes that match

Decide the command list first, then place the points for each shape.

```css
.shape {
  d: path("M4 12 C4 7.6 7.6 4 12 4 C16.4 4 20 7.6 20 12 C20 16.4 16.4 20 12 20 C7.6 20 4 16.4 4 12 Z");
  transition: d 240ms cubic-bezier(0.25, 1, 0.5, 1);
}
.shape[data-state="square"] {
  d: path("M4 12 C4 4 4 4 12 4 C20 4 20 4 20 12 C20 20 20 20 12 20 C4 20 4 20 4 12 Z");
}
```

Both are `M`, four `C`, `Z`. The square cheats: each "curve" has its control points pulled into the corner, so it looks straight.

**Step: Pick one point budget.** Say `M` plus eight `C` plus `Z`. Both shapes use exactly that. If one shape needs less detail, add points that sit on top of each other; never delete commands.

**Step: Same start, same direction.** Start both at the same spot and draw both clockwise (or both counter-clockwise). Otherwise the morph twists through itself.

**Step: Match the holes.** A shape with two holes needs two holes in the other state. Shrink an unused hole to nothing instead of removing it.

**Step: Normalise before shipping.** Convert both to absolute cubic curves (`C`, not `c` or `S`). A morph that works in Chrome and jumps in Safari almost always mixes `S` and `C`, or `l` and `L`: one browser quietly converts them, the other doesn't.

## When the shapes just don't match

For two unrelated shapes (a logo becoming a word, a blob becoming a star), a library can resample both to the same number of points. flubber (A small JavaScript library that resamples two unrelated shapes so they can morph into each other. Worth it when the morph is the product, not for an icon toggle) does this with one function, `interpolate(pathA, pathB)`, and GSAP's MorphSVG does the same inside GSAP.

The cost is a dependency and a JavaScript loop. Pay it when the morph *is* the product: a logo transformation, a shape-shifting hero. Not for a button.

*(Animating `d` repaints every frame. One hero shape is fine. Twenty morphing at once is not.)*

## Most of the time, swap instead

Play to pause. Copy to check. Menu to close. These feel like morphs, but the honest version is an icon swap (Changing one icon for another (copy to check, play to pause) by fading, scaling and blurring the old one out while the new one comes in, instead of morphing between them). Press each button a few times, quickly.

*[Interactive demo: Left: the icon just changes. Right: the old one scales to 0.3, fades and blurs as the new one arrives. Press it twice fast and it reverses smoothly.]*

Both icons are rendered at once, stacked. The outgoing one goes to `scale(0.3)`, `opacity: 0` and `blur(3px)`; the incoming one comes back from there. Because both are always on the page, pressing again mid-change just reverses the transition.

A swap reads as a real change of meaning. A timid morph between unrelated shapes, a copy icon stretching into a tick, reads as a glitch.

| Situation | Use |
|---|---|
| Two states of the same shape (chevron up and down) | Rotate it |
| Icons designed together on one line budget | A morph |
| Two different icons (copy and check, play and pause) | A swap |
| A logo or hero that shape-shifts | A morph, with flubber if the paths don't match |

## Ask for the right one

**Morph or swap (paste into your agent):**

```text
Our copy button should turn into a check when pressed.
Don't morph the paths. Render both icons stacked; outgoing goes to scale(0.3), opacity 0, blur(3px), incoming reverses that, 200-260ms with cubic-bezier(0.23, 1, 0.32, 1), interruptible.
Separately, the hero blob morphs between three shapes: author all three as M + 8 absolute C + Z, same start point, clockwise, and transition the d property. Tell me if any shape can't fit the budget.
```

**Check yourself:** Your agent morphs a hamburger icon (three M/L lines) into an X drawn as two M/L lines. It jumps instead of animating. Why?

- [ ] The duration is too short
- [x] The paths have different numbers of commands, so nothing pairs up
- [ ] transform-box is missing
- [ ] Morphs only work with circles

*Why:* Three lines have more commands than two. A native morph needs the same commands in the same order; keep three lines in both states (collapse one) or rotate the lines instead.

## In the skill

- https://school.agents.org.in/skill/svg/svg-path-morphing.md
