Every library in this category will morph any two icons you hand it. None of them will tell you when the result is worth shipping.
Here are twelve pairs, all compiled by the same pipeline, all rendering real geometry. Play them and decide which ones you would put in a product.
Six of those are refused by default. If your sorting matched, that was two numbers agreeing with your eye, and the rest of this page is about which two and why it took a while to find them.
Three hundred pairs, spread deterministically across all 2003 Lucide icons so the sample
includes genuinely unrelated things like chess-bishop and person-standing. Every one of
them compiled. Every one stayed faithful to its own reference curve, worst deviation 0.13
units on a 24 grid.
So the pipeline works on arbitrary input. That was never really in doubt.
What the same 300 also produced: 84% had a slot pairing the fitter itself called a coin toss, and 81% parked at least one slot, which means strokes appearing out of nothing rather than travelling. Two came out clean on every axis.
That is the actual state of automatic icon morphing, and it is not a criticism of any particular implementation. Correspondence between two unrelated drawings is often just undefined, and no solver recovers meaning that was never there.
The importer already reports a confidence per slot: how much better the chosen slot scored than the runner-up. Gating on it looks like the whole answer, so that is what I reached for first.
Turn on the switch above, then switch again to confidence, and watch what happens to the ordering.
menu to x, the pair this entire library was built around, scores 0.000. So does
arrow-right to arrow-down. Meanwhile circle to guitar scores 0.399, higher than
most of the pairs you would actually ship.
The reason is worth sitting with, because it took a wrong turn to see. Confidence measures
ambiguity, and an icon that is symmetric produces ambiguity by construction. x is two
diagonals with the same centroid and the same length, so nothing distinguishes them except
orientation and the fitter says so honestly. But when two slots fit equally well, either
choice usually looks fine. Ambiguity is close to the opposite of badness.
A gate built on confidence would have blocked this library's own headline morph and waved through a circle turning into a guitar.
Residual. After the aligner solves for the best rotation and scale, whatever is left
over is residual. Zero means the two shapes really are one shape moved, which is exactly
when a morph reads as an object turning rather than as one drawing dissolving into another.
chevron-down to chevron-up is a rotation and scores 0.000. circle to guitar scores
0.846.
Park ratio. How many slots have no partner. This is what "one icon is much more complex
than the other" becomes once you measure it: a three-stroke hamburger meeting a twelve-stroke
icon means nine strokes have to be born from nowhere. sun to moon scores fine on shape,
at 0.073, and still parks 8 of its 9 slots.
Refuse anything above 0.2 residual or 50% parked, and across the random 300 it admits 5.3%. Against pairs a developer would plausibly write, it admits 13 of 19. Against deliberately absurd ones, 0 of 8.
The six real pairs it stops are the right six, and two of them are checkable against decisions this project had already made by hand, months before the gate existed.
copy to check drives the copy button on every code block here. Getting it to look right
took a pinned slot assignment and a tick authored as two subpaths rather than one, both
recorded in scripts/precompile.ts with a comment explaining why. The gate flags it at 0.599
residual without being told any of that.
sun to moon is the theme toggle in the top right, and it isn't a morph. It's two Lucide
icons swapped, and apps/web/CLAUDE.md says why: a sun with eight rays has nothing in common
with a crescent, so forcing it would be a worse advert than using two icons. The gate reaches
the same conclusion from an 89% park ratio.
A geometric threshold reproducing two human judgement calls, independently, is the strongest evidence available that it measures something real.
Four answers, and picking between them is the part a compiler cannot do for you.
Ship it anyway, because you looked and you disagree. The numbers describe geometry, not
taste, and play to pause is a genuinely non-rigid morph that plenty of products use
happily.
Fix the correspondence, which is the option a runtime solver cannot offer at all. When the pairing is wrong rather than the pair, you edit the rig and commit the fix, covered in fixing correspondence.
Or use two icons and cross-fade, which for a sun and a crescent is usually the honest answer.
Or draw, rather than morph. Redraw the pair as one stroked path that passes through both
readings and animate stroke-dashoffset, and the whole problem disappears, because a dash
offset is a single number with nothing to pair against anything. A hamburger uncurling into
a cross is the usual demonstration, and a strike-through arriving over a bell is the same
trick with a mask carrying an offset copy of the line so it reads as sitting above the icon
rather than crossing it out. The cost is that the route has to be drawn by hand and has to
look deliberate, which is precisely the work this book automates away for pairs where
correspondence is findable. Nothing here can find it for you. It is the right answer
when the residual has already told you the pair is not a morph, and a wrong one whenever a
morph was available.
The twelve pairs on this page are measured by scripts/pregate.ts during the build, using
the same two functions the rest of the site runs on: importSet, which turns raw icon
paths into a rig, and buildAlignment, which works out the pairing and reports how well it
fits. Both live in lib/morph. Change the candidate list, run pnpm precompile, and the
page changes with it.
The 300-pair sweep is the same code over a wider sample. Nothing here needs the site to run.