
Morphing Icons
SkillGuides the agent in building SVG icon components that morph between any two icons by moving lines instead of crossfading. Every icon uses exactly three lines, unused ones collapse to the center, and grouped variants rotate with springs. Examples use React and Motion and respect reduced motion.
name: morphing-icons
description: Build icon components where any icon morphs into any other through SVG line transformation. Use when asked to "create morphing icons", "build icon transitions", "animate between icons", or "transform icons".
license: MIT
metadata:
author: raphael-salaja
version: "1.0.0"
source: /content/morphing-icons/index.mdx
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:
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:
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:
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:
// 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:
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:
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:
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
null,
null,
],
};
Pass:
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:
// 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:
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:
// 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:
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:
<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />
Pass:
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:
function MorphingIcon({ icon }: Props) {
return <motion.line animate={...} transition={{ duration: 0.4 }} />;
}
Pass:
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:
// Always animating rotation regardless of group
useEffect(() => {
rotation.set(definition.rotation ?? 0);
}, [definition]);
Pass:
useEffect(() => {
if (shouldRotate) {
rotation.set(definition.rotation ?? 0); // Animate
} else {
rotation.jump(definition.rotation ?? 0); // Instant
}
}, [definition, shouldRotate]);
morphing-strokelinecap-round
Lines should use strokeLinecap="round" for polished endpoints.
Fail:
<motion.line strokeLinecap="butt" />
Pass:
<motion.line strokeLinecap="round" />
morphing-aria-hidden
Icon SVGs should be aria-hidden since they're decorative.
Fail:
<svg width={size} height={size}>...</svg>
Pass:
<svg width={size} height={size} aria-hidden="true">...</svg>
Common Icon Patterns
Two-Line Icons (check, minus, equals, chevron)
Use one or two collapsed lines:
const check = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed,
],
};
Three-Line Icons (menu, asterisk, play)
Use all three lines:
const menu = {
lines: [
{ x1: 2, y1: 3.5, x2: 12, y2: 3.5 },
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 2, y1: 10.5, x2: 12, y2: 10.5 },
],
};
Point Icons (more, grip)
Use zero-length lines as dots:
const more = {
lines: [
{ x1: 3, y1: 7, x2: 3, y2: 7 },
{ x1: 7, y1: 7, x2: 7, y2: 7 },
{ x1: 11, y1: 7, x2: 11, y2: 7 },
],
};
Recommended Transition
Use exponential ease-out for smooth morphing:
const defaultTransition: Transition = {
ease: [0.19, 1, 0.22, 1],
duration: 0.4,
};
Output Format
When auditing morphing icon implementations, output findings as:
file:line - [rule-id] description of issue
Example:
components/icon/index.tsx:45 - [morphing-three-lines] Icon "check" has only 2 lines, needs collapsed third
components/icon/index.tsx:78 - [morphing-group-variants] arrow-down uses different line definitions than arrow-right
Summary Table
After findings, output a summary:
| Rule | Count | Severity |
|---|---|---|
morphing-three-lines | 2 | HIGH |
morphing-group-variants | 1 | HIGH |
morphing-reduced-motion | 1 | MEDIUM |
References
関連リソース

Micro Interaction
SkillAdds functional UI micro-interactions with Framer Motion (motion/react) and modern CSS: hover and press feedback, toggles, toasts, drawers, modals, list reorder and shared-element transitions, with 100-250ms timings, asymmetric enter and exit easing, and reduced-motion support.
iart-ai/web-animation-skills43
To Spring or Not to Spring
SkillAudits animation code for the right timing function: springs for gestures and interruptible motion, easing for system-driven state changes, linear for progress, and no animation for high-frequency interactions. Also checks spring parameters and durations. Reports file:line findings.
raphaelsalaja/skill41
Animate UI
SkillPrototyper UI's animation conventions for writing, reviewing or debugging component motion: copy-paste class strings for overlays, dialogs and sheets, press-feedback scales, state transitions, easing tokens and prefers-reduced-motion handling.
PrototyperAI/prototyper-ui7
Page Transition Animation
SkillImplements page and route transitions in the Next.js App Router and fixes Framer Motion exit animations that never fire on navigation, using template.tsx with a pathname-keyed AnimatePresence and FrozenRouter, or the View Transitions API via next-view-transitions.
iart-ai/web-animation-skills45
Mastering AnimatePresence
SkillAudits Motion (Framer Motion) code against AnimatePresence rules: wrapping conditional exits, exit props and keys, the useIsPresent and usePresence hooks, choosing a mode, and coordinating nested exits. Reports findings as file:line. For React projects.
raphaelsalaja/skill41
shadcn/ui
SkillOfficial shadcn/ui skill: uses the shadcn CLI to read project config and component docs, then adds, searches, composes and fixes components following styling, form and accessibility rules. Can also start new projects from presets.
shadcn-ui/ui126k