Skip to content
WANCRAFT

A.126Buttons & Controls

Morph Icon Button

An icon button whose glyph morphs between two states by interpolating SVG paths — play/pause, menu/close, heart and more.

NewReactMotionSVGTailwindintermediate

Overview

Morph Icon Button animates the glyph itself, not a crossfade. Each built-in pair — play / pause, menu / close, plus / minus, chevron up / down and heart outline / filled — is drawn with matching path commands point for point (the play triangle is split into two quads that line up with the pause bars; the middle menu bar folds onto the cross), so motion interpolates the path data directly on a spring or a tween, optionally with a rotation. Bring your own from / to paths too: if their command structure differs the button detects it and crossfades instead of tearing. It is controlled or uncontrolled, and the accessibility follows the pattern: a pair whose meaning changes (Play → Pause) switches its accessible name, while a pair with one meaning (Like) becomes a toggle with aria-pressed.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/morph-icon-button.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/morph-icon-button.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/morph-icon-button.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { MorphIconButton } from "@/components/ui/morph-icon-button";

export function MorphIconButtonExample() {
  return (
    <MorphIconButton onActiveChange={(playing) => console.log(playing)} />
  );
}

03Props

PropTypeDefaultDescription
icon"play-pause" | "menu-close" | "plus-minus" | "chevron" | "heart""play-pause"Built-in glyph pair (ignored when from and to are set).
from / tostring—Custom inactive / active paths in a 24×24 viewBox. Matching command structure interpolates; otherwise they crossfade.
paint"stroke" | "fill"—How custom paths are painted (built-ins choose their own).
activeboolean—Controlled state.
defaultActivebooleanfalseInitial state when uncontrolled.
onActiveChange(active: boolean) => void—Called with the next state.
label / activeLabelstring—Accessible names per state. If they differ the name switches (no aria-pressed); if equal the button uses aria-pressed. Built-ins have sensible defaults.
transition"spring" | "tween""spring"Interpolate on a spring or a fixed-duration tween.
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 320, damping: 26, mass: 0.8 }Spring for the morph and rotation.
durationnumber320Tween duration in ms.
rotatenumber0Extra rotation of the glyph in the active state, in degrees.
sizenumber44Button size in px (the glyph is half of it).
strokeWidthnumber1.75Stroke width for stroked glyphs, in viewBox units.
variant"outline" | "solid" | "ghost""outline"Surface style; outline and ghost tint the glyph when active.
accentstring"#FF7448"Active tint, solid fill and focus ring.
...propsButtonHTMLAttributes—Forwarded to the <button>; the ref points to it.

04Features

  • 01True path morphing: motion interpolates the d attribute number by number
  • 02Five built-in pairs with point-for-point matched paths
  • 03Custom from / to paths, with a crossfade fallback when structures differ
  • 04Spring or tween, plus an optional rotation in the active state
  • 05Controlled or uncontrolled; outline, solid and ghost variants
motion · springmotion · tweeninput · clickinput · keyboard

05Accessibility

  • A native <button>; the SVG is aria-hidden.
  • Pairs whose meaning changes switch their accessible name ("Play" ↔ "Pause", "Open menu" ↔ "Close menu"); single-meaning pairs ("Like") are toggles with aria-pressed — never both, so the state is not announced twice.
  • Visible focus-visible ring in the accent colour.
  • With prefers-reduced-motion the glyph switches instantly.

06Limitations

  • Custom paths only morph when they use the same commands and the same number of values; convert arcs and shorthand commands first.
  • The menu / close pair only swaps the glyph — wire aria-expanded / aria-controls to your menu yourself.