Morphing SVG Icon
A seamless vertex-to-vertex transformation between two distinct icons (e.g., Play -> Pause, Menu -> Close), fluidly shifting shapes rather than just a hard cut swap.
Use
Media players
Avoid
When icons have drastically different structural nodes that result in chaotic spaghetti morphs
Safer Alternative
No safer default listed.
Risk Level
Low risk
Live Demo
What It Is
A seamless vertex-to-vertex transformation between two distinct icons (e.g., Play -> Pause, Menu -> Close), fluidly shifting shapes rather than just a hard cut swap.
✓When to Use
- Media players
- Hamburger menus
- Interactive toggle buttons
✕When NOT to Use
- When icons have drastically different structural nodes that result in chaotic spaghetti morphs
Configuration Tips
- 01Ensure SVG paths have the same number of data points, or rely on Framer Motion's powerful cross-morph capabilities for simpler SVGs.
You've Seen It In
AI Implementation Prompts
Move from a fast scaffold to production details, then tune timing and edge states.
Use framer-motion to animate an SVG path 'd' attribute smoothly from a play triangle to two vertical pause bars.
Reference Implementation
The same implementation approach used by the live preview, kept compact enough to inspect and adapt.
"use client";
import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';
export function MorphingSvgIconDemo({ isPlaying = false }: { isPlaying?: boolean }) {
const [isPlayingState, setIsPlayingState] = useState(false);
useEffect(() => {
if (!isPlaying) {
setIsPlayingState(false);
return;
}
const loop = setInterval(() => {
setIsPlayingState(prev => !prev);
}, 1500);
return () => clearInterval(loop);
}, [isPlaying]);
"text-stone-400 italic">// Using SVG path morphing via Framer Motion.
"text-stone-400 italic">// Both states must have the same number of path commands (ideally).
"text-stone-400 italic">// Or we can rely on Framer Motion's smart morphing if SVGs are simple enough.
"text-stone-400 italic">// For play to pause, a common trick is animating one polygon into two rectangles.
"text-stone-400 italic">// However, to keep it simple and robust, we can morph two lines that make up the play triangle.
return (
<div className="flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl bg-white p-4 shadow-sm ring-1 ring-stone-200">
<button
onClick={() => setIsPlayingState(!isPlayingState)}
className="w-20 h-20 rounded-full bg-indigo-50 flex items-center justify-center text-indigo-600 hover:bg-indigo-100 transition-colors focus:outline-none focus:ring-4 focus:ring-indigo-100 outline-none shadow-sm"
>
<motion.svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="currentColor"
>
{"text-stone-400 italic">/*
We use two SVG paths that make up the top and bottom halves of the Play triangle,
which then morph into the left and right Pause bars.
*/}
<motion.path
initial={false}
animate={{
d: isPlayingState
? "M9 6h4v20H9z" "text-stone-400 italic">// Left Pause Bar
: "M10 6L24 16L10 16z" "text-stone-400 italic">// Top half of Play Triangle
}}
transition={{ duration: 0.4, ease: "anticipate" }}
/>
<motion.path
initial={false}
animate={{
d: isPlayingState
? "M19 6h4v20h-4z" "text-stone-400 italic">// Right Pause Bar
: "M10 26L24 16L10 16z" "text-stone-400 italic">// Bottom half of Play Triangle
}}
transition={{ duration: 0.4, ease: "anticipate" }}
/>
</motion.svg>
</button>
</div>
);
}