Build icon components that morph between SVG shapes with smooth, line-based transformation.
Real-world examples
Live HTML demos for this skill — rendered directly in the page. 4 examples.
- 01
Any icon → any icon
Click a target: the hero morphs through shared 3-line SVG coordinates — real shape transforms, not crossfades.
- 02
Grouped spring rotation
Plus↔cross and arrow family share groups so rotation springs; switching out of a group jumps rotation instantly.
- 03
Collapsed unused lines
Check, minus, and equals keep exactly three lines — extras collapse to an invisible center point so morphs stay continuous.
- 04
Toolbar control morphs
Play/pause, mute, and menu/close toggle as morphing icons with round caps, aria-hidden SVGs, and reduced-motion respect.
Skill markdown
# Morphing Icons
Build icons that transform through actual shape transformation, not crossfades. Any icon can morph into any other because they share the same underlying 3-line structure.
## Core Concept
Every icon is composed of exactly **three SVG lines**. Icons that need fewer lines collapse the extras to invisible center points. This constraint enables seamless morphing between any two icons.
## Architecture
### 1. Line Definition
Each line has coordinates and optional opacity:
```ts
interface IconLine {
x1: number;
y1: number;
x2: number;
y2: number;
opacity?: number;
}
```
### 2. Collapsed Lines
Icons needing fewer than 3 lines use collapsed lines—zero-length lines at the center:
```ts
const CENTER = 7; // Center of 14x14 viewbox
const collapsed: IconLine = {
x1: CENTER,
y1: CENTER,
x2: CENTER,
y2: CENTER,
opacity: 0,
};
```
### 3. Icon Definition
Each icon has exactly 3 lines, optional rotation, and optional group:
```ts
interface IconDefinition {
lines: [IconLine, IconLine, IconLine];
rotation?: number;
group?: string;
}
```
### 4. Rotation Groups
Icons sharing a `group` animate rotation when transitioning between them. Icons without matching groups jump to the new rotation instantly:
```ts
// These rotate smoothly between each other
{ lines: plusLines, rotation: 0, group: "plus-cross" } // plus
{ lines: plusLines, rotation: 45, group: "plus-cross" } // cross
// These rotate smoothly between each other
{ lines: arrowLines, rotation: 0, group: "arrow" } // arrow-right
{ lines: arrowLines, rotation: 90, group: "arrow" } // arrow-down
{ lines: arrowLines, rotation: 180, group: "arrow" } // arrow-left
{ lines: arrowLines, rotation: -90, group: "arrow" } // arrow-up
```
## Implementation Rules
### `morphing-three-lines`
Every icon MUST use exactly 3 lines. No more, no fewer.
**Fail:**
```ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
], // Only 2 lines
};
```
**Pass:**
```ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed, // Third line collapsed
],
};
```
### `morphing-use-collapsed`
Unused lines must use the collapsed constant, not omission or null.
**Fail:**
```ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
null,
null,
],
};
```
**Pass:**
```ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
collapsed,
collapsed,
],
};
```
### `morphing-consistent-viewbox`
All icons must use the same viewBox (14x14 recommended).
**Fail:**
```ts
// Mixing viewbox scales
const icon1 = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] }; // 14x14
const icon2 = { lines: [{ x1: 4, y1: 14, x2: 24, y2: 14 }, ...] }; // 28x28
```
**Pass:**
```ts
const VIEWBOX_SIZE = 14;
const CENTER = 7;
// All coordinates within 0-14 range
```
### `morphing-group-variants`
Icons that are rotational variants MUST share the same group and base lines.
**Fail:**
```ts
// Different line definitions for arrows
const arrowRight = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] };
const arrowDown = { lines: [{ x1: 7, y1: 2, x2: 7, y2: 12 }, ...] }; // Different!
```
**Pass:**
```ts
const arrowLines: [IconLine, IconLine, IconLine] = [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 7.5, y1: 2.5, x2: 12, y2: 7 },
{ x1: 7.5, y1: 11.5, x2: 12, y2: 7 },
];
const icons = {
"arrow-right": { lines: arrowLines, rotation: 0, group: "arrow" },
"arrow-down": { lines: arrowLines, rotation: 90, group: "arrow" },
"arrow-left": { lines: arrowLines, rotation: 180, group: "arrow" },
"arrow-up": { lines: arrowLines, rotation: -90, group: "arrow" },
};
```
### `morphing-spring-rotation`
Rotation between grouped icons should use spring physics for natural motion.
**Fail:**
```tsx
<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />
```
**Pass:**
```tsx
const rotation = useSpring(definition.rotation ?? 0, activeTransition);
<motion.g style={{ rotate: rotation, transformOrigin: "center" }} />
```
### `morphing-reduced-motion`
Respect `prefers-reduced-motion` by disabling animations.
**Fail:**
```tsx
function MorphingIcon({ icon }: Props) {
return <motion.line animate={...} transition={{ duration: 0.4 }} />;
}
```
**Pass:**
```tsx
function MorphingIcon({ icon }: Props) {
const reducedMotion = useReducedMotion() ?? false;
const activeTransition = reducedMotion ? { duration: 0 } : transition;
return <motion.line animate={...} transition={activeTransition} />;
}
```
### `morphing-jump-non-grouped`
When transitioning between icons NOT in the same group, rotation should jump instantly.
**Fail:**
```tsx
// Always animating rotation regardless of group
useEffect(() => {
rotation.set(definition.rotation ?? 0);
}, [definition]);
```
**Pass:**
```tsx
useEffect(() => {
if (shouldRotate) {
rotation.set(definition.rotation ?? 0); // Animate
}
…More from raphaelsalaja
- 12 Principles Of AnimationApply Disney's 12 animation principles to web interfaces to make motion feel natural, organic, and human.
- Generating Sounds With AiAudit Web Audio API code for procedural sound synthesis quality, UX decisions, and parameter best practices.
- Mastering Animate PresenceAudit Motion and Framer Motion exit/presence patterns with practical fixes for AnimatePresence usage.
- Pseudo ElementsAudit CSS pseudo-elements and View Transitions usage for hover effects, decorative layers, and transitions.
- Sounds On The WebAudit interface sound feedback for UX quality, accessibility, and practical implementation patterns.
- To Spring Or Not To SpringAudit animation timing choices to decide when springs versus easing curves produce better motion.