morphing-icons — Claude Code skill: what it does, how to install
Builds icon components that transform through direct SVG line motion instead of crossfades, relying on a shared three-line structure.
npx skills add raphaelsalaja/skill --skill morphing-icons -a claude-codeWhat morphing-icons does
The skill enforces an architecture where every icon consists of exactly three SVG lines. If a shape requires fewer strokes, such as a checkmark or minus sign, extra lines collapse into zero-length points at the center of the coordinate box (14x14 recommended). Because the element count stays identical, any icon transforms directly into another.
It includes rules for rotational groups, spring physics, and accessibility. Variants sharing a group property animate rotation smoothly, while unrelated icons jump to their target angle instantly. Components must also check useReducedMotion to disable transitions when the user requests minimal motion.
Omitting lines or passing null breaks the transition: unused lines must explicitly use coordinates with zero length and zero opacity. The skill also checks for aria-hidden="true" and round line caps on all icon definitions.
What it helps with
- animate transformations between SVG icons without crossfading
- structure icon geometry using exactly three lines and collapsed points
- implement spring-driven rotational transitions for icon variants
- respect prefers-reduced-motion preferences in animated icon components
Example prompts for the agent
- Create a morphing icon component that transitions between a plus and a cross.
- Audit my icon definitions using the morphing-three-lines rules.
- Set up directional arrows with shared rotation groups and spring physics.
How to install morphing-icons
1Skills CLI (Vercel)
npx skills add raphaelsalaja/skill --skill morphing-icons -a claude-code2Manually: put the files in place
~/.claude/skills/morphing-icons/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/morphing-icons/
1Skills CLI (Vercel)
npx skills add raphaelsalaja/skill --skill morphing-icons -a codex2Manually: put the files in place
.agents/skills/morphing-icons/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add raphaelsalaja/skill --skill morphing-icons -a cursor2Manually: put the files in place
.cursor/skills/morphing-icons/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install raphaelsalaja/skill morphing-icons2Manually: put the files in place
.github/skills/morphing-icons/SKILL.md1Skills CLI (Vercel)
npx skills add raphaelsalaja/skill --skill morphing-icons -a gemini-cli2Platform CLI
gemini skills install https://github.com/raphaelsalaja/skill --path skills/morphing-iconsManually: put the files in place
.devin/skills/morphing-icons/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add raphaelsalaja/skill --skill morphing-icons -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about morphing-icons
How to install morphing-icons in Claude Code?
Run npx skills add raphaelsalaja/skill --skill morphing-icons -a claude-code in your terminal. This stores the definition in ~/.claude/skills/morphing-icons/SKILL.md.
What does morphing-icons do?
It provides architectural rules and code patterns to build SVG icons that physically morph into each other through three coordinated lines.
Why must icons with fewer lines use collapsed lines?
Interpolation requires matching element counts across states. Unused strokes collapse into zero-length points at the center rather than being removed from the DOM.
Source text
SKILL.md · MIT
---
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:
```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
} else {
rotation.jump(definition.rotation ?? 0); // Instant
}
}, [definition, shouldRotate]);
```
### `morphing-strokelinecap-round`
Lines should use `strokeLinecap="round"` for polished endpoints.
**Fail:**
```tsx
<motion.line strokeLinecap="butt" />
```
**Pass:**
```tsx
<motion.line strokeLinecap="round" />
```
### `morphing-aria-hidden`
Icon SVGs should be `aria-hidden` since they're decorative.
**Fail:**
```tsx
<svg width={size} height={size}>...</svg>
```
**Pass:**
```tsx
<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:
```ts
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:
```ts
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:
```ts
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:
```ts
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
- [Benji's experiments with Claude](https://benji.org/morphing-icons-with-claude)
- [Motion useSpring](https://motion.dev/docs/react-use-spring)
- [SVG Line Element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
© raphael-salaja · MIT · raphaelsalaja/skill