Documentation Index
Fetch the complete documentation index at: https://mintlify.com/raphaelsalaja/userinterface-wiki/llms.txt
Use this file to discover all available pages before exploring further.
Morphing Icons
Icons that transform through actual shape transformation, not crossfades. Inspired by Benji’s experiments with Claude.
Core Concept
Every icon uses exactly three SVG lines. Icons that need fewer lines collapse the extras to invisible points. This means any icon can morph into any other since they share the same underlying structure.
Three-Line Structure
All icons are defined with three lines, each with coordinates and optional opacity:
interface IconLine {
x1: number;
y1: number;
x2: number;
y2: number;
opacity?: number;
}
interface IconDefinition {
lines: [IconLine, IconLine, IconLine];
rotation?: number;
group?: string;
}
Collapsed Lines
When an icon needs fewer than three lines, unused lines collapse to a single point at the center with zero opacity:
const CENTER = 7;
const collapsed: IconLine = {
x1: CENTER,
y1: CENTER,
x2: CENTER,
y2: CENTER,
opacity: 0,
};
Example Icons
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 },
],
}
Minus Icon (1 line + 2 collapsed)
minus: {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
collapsed,
collapsed
],
}
Check Icon (2 lines + 1 collapsed)
check: {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed,
],
}
Rotation Groups
Icons with shared base shapes can rotate between variants instead of morphing lines. This is controlled by the group property:
// Plus and cross share the same lines but rotate
const plusLines: [IconLine, IconLine, IconLine] = [
{ x1: 7, y1: 2, x2: 7, y2: 12 },
{ x1: 2, y1: 7, x2: 12, y2: 7 },
collapsed,
];
plus: { lines: plusLines, rotation: 0, group: "plus-cross" },
cross: { lines: plusLines, rotation: 45, group: "plus-cross" },
When transitioning between icons in the same group, the component rotates the existing lines rather than morphing them:
const shouldRotate = useMemo(() => {
const prev = prevDefinitionRef.current;
return prev.group && definition.group && prev.group === definition.group;
}, [definition]);
Animation Implementation
The morphing effect uses motion’s spring physics to animate line coordinates:
function AnimatedLine({ line, transition }: AnimatedLineProps) {
return (
<motion.line
animate={{
x1: line.x1,
y1: line.y1,
x2: line.x2,
y2: line.y2,
opacity: line.opacity ?? 1,
}}
transition={transition}
strokeLinecap="round"
/>
);
}
Default transition uses a custom easing curve for smooth, natural movement:
const defaultTransition: Transition = {
ease: [0.19, 1, 0.22, 1],
duration: 0.4,
};
Accessibility
The component respects user motion preferences:
const reducedMotion = useReducedMotion() ?? false;
const activeTransition = reducedMotion ? { duration: 0 } : transition;
Usage Example
import { MorphingIcon } from "@/components/morphing-icon";
function IconButton() {
const [icon, setIcon] = useState("menu");
return (
<button onClick={() => setIcon(icon === "menu" ? "cross" : "menu")}>
<MorphingIcon icon={icon} size={24} strokeWidth={1.5} />
</button>
);
}
Available Icons
The system supports 27 icons:
- Navigation: menu, cross, chevron-right, chevron-down, chevron-left, chevron-up, arrow-right, arrow-down, arrow-left, arrow-up
- Actions: plus, minus, check, play, pause, download, upload, external
- Symbols: equals, asterisk, more, grip, slash, corner
Reference
Inspired by Benji’s experiments with Claude, demonstrating how AI-assisted design can explore systematic constraints that lead to elegant solutions.