morphicons: any stroke icon morphs into any other, without declaring rotations
Any icon morphs into any other — universal morphing for stroke-based icons with spring physics. Zero dependencies, ~7 KB gzip.
At a glance
- What is it?
- A zero-dependency TypeScript library that solves the optimal rotation between two icon shapes in closed form and animates between them with spring physics, shipping bindings for React, Vue, Svelte, React Native, a custom element and Astro.
- Who is it for?
- Adopt morphicons if you already render Lucide, Tabler, Heroicons or Iconoir data and want state-driven icon transitions without hand-declared rotation groups; it fits React 18+, Vue 3.3+, Svelte 5 and React Native 0.71+ apps, and the custom element binding needs no framework peer at all. Skip it if your icons are filled or multi-color, if you need path-by-path correspondence between arbitrary artwork, or if you are on an older Svelte or React major.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The rotation-group problem morphicons removes
Most icon morphs fall into one of two camps. The first interpolates raw path coordinates, so a shape in transit shrinks and shears before it settles; the README calls this out directly. The second asks the developer to declare which parts of an icon rotate and by how much, pair by pair. That second approach scales badly: a menu-to-close transition is easy, but a library of forty icons crossed with each other is 1,560 pairs, and each one needs a human decision.
morphicons targets stroke-based icon sets specifically: Lucide, Tabler, Heroicons, Iconoir, or your own `d` strings. The README's example is `arrow-right` to `arrow-down`, which the library resolves to a 90 degree rotation on its own, with nobody writing 90 anywhere. The pitch is that state lives outside the component and the animation is an implementation detail picked up when the prop changes. If you have ever wrapped an icon in `AnimatePresence` with a key and a from/to pair just to get a toggle button to feel right, this is aimed at you.
Procrustes alignment and polar interpolation, in practice
The mechanism named in the README is 2D Procrustes plus polar interpolation. Procrustes analysis finds the optimal similarity transform (rotation, scale, translation) that aligns one point set to another; morphicons computes that optimum in closed form and interpolates in the space where it is natural. The practical consequence is a branching rule: if two icons are congruent under rotation, the transition rotates; if they are not, the paths morph inside the already aligned frame. That is why `arrow-right` to `arrow-down` rotates rather than deforming.
The component exposes three modes, and the split matters more than it looks. Uncontrolled mode is the 90 percent case: you change the `icon` prop and the component animates. Controlled mode takes `from`, `to` and an explicit `progress` value and applies no spring at all, which is what you want when a gesture or scroll position drives the animation and a spring would fight your input. Imperative mode hands you a ref with `morphTo` (animates) and `set` (jumps without animating), for sequences where one transition should start after another finishes. The three modes share the same presentation props, so switching between them does not change your markup.
Installing morphicons and getting a real toggle working
The README installs with bun, and notes npm and pnpm work too. The package is ESM only, which is worth checking against your build setup before anything else.
bun add morphicons # or npm install / pnpm addThe critical detail is that morphicons consumes icon data, not icon components. For Lucide that means the vanilla `lucide` package, whose exports are `IconNode` objects. The framework packages (`lucide-react`, `lucide-vue-next`, `@lucide/svelte`, `lucide-react-native`) export components, and `MorphIcon` cannot consume those. The README is explicit that the two can coexist in one app and both tree-shake, as long as you keep their versions aligned so the icons you morph match the icons you render statically.
import { MorphIcon } from "morphicons/react";
import { Menu, X } from "lucide"; // data, not components
<button onClick={() => setOpen(o => !o)} aria-expanded={open}>
<MorphIcon icon={open ? X : Menu} />
</button>That snippet is the whole integration: no wrapper, no key, no from/to pair. When `open` flips, the `icon` prop changes and the component animates. You should see the menu glyph rotate and settle into the close glyph rather than cross-fade. The README also states the same three modes exist in the Vue and Svelte bindings with matching prop names, and that the custom element (`morphicons/element`) and the Astro shell (`morphicons/astro`) require no peer dependency at all.
Peer dependencies, SSR and the accessibility defaults
The peer dependencies are optional and scoped per binding: `react` >= 18, `vue` >= 3.3, `svelte` >= 5, and `react-native` >= 0.71 with `react-native-svg` >= 14. You only pull in the ones you import. Svelte 5 is the floor, so a Svelte 4 codebase cannot use the Svelte binding without a major upgrade; the component ships as `.svelte` source compiled by your bundler through the `svelte` export condition. The README notes the Vue binding is a plain render function with no SFC compiler or JSX involved, and that the Svelte binding is fully typed via `svelte/elements`, so SVG attributes, events and ARIA autocomplete and typos fail the build.
SSR behavior is stated plainly: the server emits the exact static SVG, so there is no flash and no layout shift, and the runtime is born on hydration. That holds for Nuxt and SvelteKit out of the box according to the README. Accessibility defaults are `aria-hidden` unless you pass `label`, in which case the component emits `role="img"` with a `<title>`. One default deserves scrutiny: morphs play regardless of the OS reduce-motion setting, and you opt into honoring it with `reducedMotion="user"`. That is the opposite of what many teams assume, and it is a deliberate choice rather than an oversight, but it means an audit will flag it unless you set the prop.
Where morphicons is the wrong tool
The scope is stroke-based icons, and that boundary is real. Filled icons, duotone sets, multi-color illustrations and logos with separate fill paths are outside what the alignment model is described as handling. If your brand mark needs to become a hamburger, this is not the library for that job.
The second limitation is correspondence. Procrustes finds the best global similarity between two shapes, which is exactly why rotations emerge without declaration, but it also means the library is not solving per-path semantic matching. Two icons with very different subpath counts will still morph in the aligned frame, and the README does not document what that looks like for pathological pairs. There is no documented rollback or fallback behavior for a pair that reads badly, and no documented way to override the computed rotation for a specific pair. If you need art-directed control over which stroke becomes which, you are outside the design intent.
The third is the icon-data constraint. If your codebase is built around `lucide-react` components and you would rather not add the vanilla `lucide` package alongside it, morphicons asks you to change that. The README frames the coexistence as a feature, and it is, but it is still a second dependency to keep version-aligned.
How it differs from animating SVG paths yourself
The obvious alternative is hand-rolled path interpolation: pull the `d` strings, parse them, interpolate the numbers, drive the result with a spring from a general animation library. That approach works and has no dependency cost beyond what you already have. The difference in approach is where the intelligence sits. Manual interpolation assumes the two paths are already in correspondence, so you supply the pairing, the direction and any rotation yourself. morphicons computes the optimal similarity in closed form and decides whether the transition should rotate or morph, which is the part that does not scale by hand.
A second alternative is a general-purpose morph library built around SVG path morphing with explicit from/to declarations. Those give you more art direction and handle filled shapes, at the cost of declaring every pair. The trade is control against coverage: if you have a handful of hero transitions you want pixel-perfect, declare them; if you have a whole icon set and want every combination to behave sensibly, the computed alignment is the more economical answer. morphicons also ships a live playground at morphicons.com, which is the fastest way to judge whether the computed results match your taste before adopting anything.
Licence, release cadence and what an upgrade costs
The package is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are preserved. That is the extent of what the repository states; it is not legal advice and your counsel should confirm obligations for your distribution model. The repository includes a `SECURITY.md` and a `CODE_OF_CONDUCT.md`, so there is a stated channel for reporting issues.
On maintenance: the last push was on 2026-09-14, and the most recent release is v1.7.1 from 2026-08-28, following v1.7.0 on 2026-08-13 and v1.6.0 on 2026-08-08. The repository is not archived. Upgrade cost is dominated by the peer floors rather than the library itself: the package is ESM only, Svelte 5 is required for that binding, and the icon data package versions must stay aligned with any component package you render statically. There is a `CHANGELOG.md` at the repository root, which is where breaking changes between the 1.6 and 1.7 lines would be recorded. The MIT terms mean a fork is always available if the cadence changes.
Editorial conclusion
Adopt morphicons if you already render Lucide, Tabler, Heroicons or Iconoir data and want state-driven icon transitions without hand-declared rotation groups; it fits React 18+, Vue 3.3+, Svelte 5 and React Native 0.71+ apps, and the custom element binding needs no framework peer at all. Skip it if your icons are filled or multi-color, if you need path-by-path correspondence between arbitrary artwork, or if you are on an older Svelte or React major. Before wiring it into a design system, verify that your icon package exports data rather than components, check that the peer versions stay aligned between the static and morphing imports, and decide explicitly whether you want reducedMotion="user" or the default that ignores the OS setting.
Frequently asked questions
How do I install morphicons and use it in React?
Install it with bun, npm or pnpm; the package is ESM only. Then import MorphIcon from morphicons/react and pass icon data from the vanilla lucide package, changing the icon prop to trigger a morph.
Can morphicons use lucide-react components instead of icon data?
No. The README states that MorphIcon consumes IconNode data or a raw d string, and that lucide-react, lucide-vue-next, @lucide/svelte and lucide-react-native export components the component cannot consume. Use the vanilla lucide package for the icons you morph, keeping versions aligned with any component package you keep for static icons.
Does morphicons respect the operating system reduce-motion setting?
By default it does not; morphs play regardless of the OS setting. You opt into honoring it with the reducedMotion="user" prop, which the README documents alongside the other bindings.
Which frameworks does morphicons support?
The package ships bindings for react (>= 18), vue (>= 3.3), svelte (>= 5) and react-native (>= 0.71 with react-native-svg >= 14), plus a morph-icon custom element and an Astro SSR shell that need no peer dependency at all.
Community notes