Skip to content
Design
Skill

/morphing-icons

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".

BOOST
From plugin
raphaelsalaja-skill
417 skills
Install
$ npx -y skills add raphaelsalaja/skill --skill morphing-icons --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/morphing-icons

Context preview

The summary Claude sees to decide when to auto-load this skill.

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".

SKILL.md

morphing-icons.SKILL.md
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)

Us

Read more
Ships withraphaelsalaja-skill

A skill file based on the articles written on userinterface.wiki. Designed for designers and engineers to help them build better user interfaces.

Get the whole plugin

Other skills on raphaelsalaja-skill.