# shapcn docs > Morph icons like a pro. Change the icon name. shapcn animates the rest. The engine plans every morph live, shape by shape. Source: https://shapcn.com/docs # Docs shapcn morphs one icon into another. Change the `name` of a `` element and the icon moves the way an animator would draw it. No animation is pre-built. The engine plans every frame when you ask. ## Quick start Add one script and one tag. The script defines the `` element and loads the Lucide icons. ```html ``` The demo above changes its name every two seconds. To do the same in your page, set the `name` attribute. ```html ``` The icon morphs from `menu` to `x`. Set `name` to an empty string and the icon hides with an effect. Set a name again and it shows. ## What is in the docs - [Install](https://shapcn.com/docs/install.md): the script tag and the npm package. - [Element](https://shapcn.com/docs/element.md): every attribute of ``, with a live demo each. - [Feel](https://shapcn.com/docs/feel.md): the easing presets `smooth`, `spring`, and `elastic`. - [Effects](https://shapcn.com/docs/effects.md): the seven ways an icon appears and disappears. - [Icons](https://shapcn.com/docs/icons.md): Lucide, the 12 icon sets, and your own icons. - [JavaScript](https://shapcn.com/docs/javascript.md): `planMorph`, `planAlive`, `alive`, `defineMorphIcon`, and `registerIcons`. - [Frameworks](https://shapcn.com/docs/frameworks.md): plain HTML, React, Vue, and Svelte. - [Accessibility](https://shapcn.com/docs/accessibility.md): reduced motion, focus, and aria. ## Back to the site - [Examples](/#examples) - [Playground](/#play) - [Compare](/#compare): plain point blending next to shapcn, frame by frame. - [How it works](/#how) - [Install](/#install) - [GitHub](https://github.com/seahorsecastle/shapcn) ## Markdown and LLM files Every page has a Markdown twin. Add `.md` to the path, for example `/docs/element.md`. The file [/llms.txt](https://shapcn.com/llms.txt) lists the pages. The file [/llms-full.txt](https://shapcn.com/llms-full.txt) holds all pages in one file. --- Source: https://shapcn.com/docs/install # Install ## Script tag The script tag works today. It is one file. It defines `` and loads the Lucide icons. ```html ``` The script also sets `window.shapcn`. The object holds these names: `DEFAULTS`, `easings`, `registerIcons`, `registerFamily`, `registerFamilyFile`, `getIconInfo`, `getFamily`, `getIconNames`, `iconToSVG`, `planMorph`, `planAlive`, `MorphIcon`, and `defineMorphIcon`. The script tag build has the Lucide icons only. It does not expose `registerSVGs`, `registerFamilyIcons`, or `alive`. Use the npm package for those. Use `window.shapcn` from a module script. A module script runs after the page loads, so the API is ready. ```html ``` ## npm Script tag works today. npm package is in progress. Source is on GitHub. ```sh npm i shape-aware-morph ``` The package is ES modules only. In Node, `require('shape-aware-morph')` works only on Node 22.12 or newer. Use `import` on older versions. The core has no icon data. You import only the icons you use (see [Icons](https://shapcn.com/docs/icons.md)). The core alone is about 181 kB minified (70 kB gzipped). Each icon adds a few hundred bytes. Use it in a bundler: ```js import { registerLucide, defineMorphIcon } from 'shape-aware-morph'; await registerLucide(); // loads the Lucide icons defineMorphIcon(); // defines ``` Use it from a CDN as one file. It loads the Lucide icons and defines `` for you. It also sets `window.shapcn`. ```html ``` jsDelivr works too: `https://cdn.jsdelivr.net/npm/shape-aware-morph@0.1.0`. ## Use the source today The npm package is not published yet. The source is on [GitHub](https://github.com/seahorsecastle/shapcn). Build it in three steps. ```sh git clone https://github.com/seahorsecastle/shapcn cd shapcn/package npm install && npm run build ``` Then import from the package folder, or use `dist/morph-icon.js`. ## License shapcn is MIT. Lucide icon data is ISC. The other icon sets keep their own licenses (see [Icons](https://shapcn.com/docs/icons.md)). --- Source: https://shapcn.com/docs/element # Element `` is a custom element. Change its `name` and the icon morphs. Change any other attribute and the element updates. ```html ``` The element sets `display: inline-block` and `line-height: 0` on itself if you set no value. The default color is `currentColor`, so the CSS `color` of the parent sets the stroke color. ## Attributes | Attribute | Default | Meaning | | --- | --- | --- | | `name` | none | The icon name, for example `mic`. An empty name hides the icon with the effect. A name after an empty one shows it with the effect. | | `size` | `24` | Width and height in pixels. | | `color` | `currentColor` | Stroke color. | | `stroke-width` | `2` | Stroke width on the 24 grid. | | `duration` | `450` | Morph time in milliseconds. | | `easing` | `easeInOutCubic` | One of `linear`, `easeInOutCubic`, `easeOutCubic`, `smooth`, `spring`, `elastic`, `gentle`. | | `stagger` | `0` | Spreads the start of the strokes over part of the time. A number from 0 to 0.99. | | `effect` | `implode` to hide, `expand` to show | The effect for hide and show: `implode`, `explode`, `assemble`, `draw`, `erase`, `scale`, `expand`. | | `hover` | none | Set `alive` to play a short life animation on pointer enter and on keyboard focus. It lasts 600 ms. | The element observes these nine attributes and no others. If you remove an attribute, the element keeps the last value. ## name The icon to show. Set it again and the icon morphs to the new one. The first demo morphs between two icons. ```html ``` An empty `name` hides the icon with the `effect`. A name after an empty one shows it. See [Effects](https://shapcn.com/docs/effects.md). The element also has a `name` property. `el.name = 'mic-off'` sets the attribute. ## size Width and height in pixels. The default is `24`. The icon draws on a 24 by 24 grid and scales to the size. ```html ``` ## color The stroke color. Any CSS color works. The default `currentColor` follows the CSS `color` of the parent. ```html ``` ## stroke-width The stroke width on the 24 grid. The default is `2`. ```html ``` A family with a stroke width other than 2 (for example Phosphor) draws with its own width. ## duration Morph time in milliseconds. The default is `450`. ```html ``` ## easing The speed curve of the morph. The default is `easeInOutCubic`. The values are `linear`, `easeInOutCubic`, `easeOutCubic`, `smooth`, `spring`, `elastic`, and `gentle`. The page [Feel](https://shapcn.com/docs/feel.md) covers `smooth`, `spring`, and `elastic`. ```html ``` ## stagger Spreads the start of the strokes over part of the time. A number from 0 to 0.99. The default is `0`: all strokes start together. At `0.9`, each stroke starts later than the one before it. ```html ``` ## effect The effect for hide and show. Set `name` to an empty string to hide. Set a name to show. Each of the seven names works in both directions. The default is `implode` to hide and `expand` to show. See [Effects](https://shapcn.com/docs/effects.md) for all seven. ```html ``` ## hover Set `hover="alive"` and the icon plays a short life animation on pointer enter and on keyboard focus. It lasts 600 ms. The icon looks the same at the end. Hover over the icons below, or press Tab to focus a button. ```html ``` The engine picks one of four moves from the shape of the icon: `radiate`, `ripple`, `swing`, or `pop`. The bell swings, the sun radiates, the Wi-Fi icon ripples, and the heart pops. Focus counts on the element or on its nearest button or link. The page [Accessibility](https://shapcn.com/docs/accessibility.md) has more. ## Methods The element has four methods. Each returns a Promise. | Method | What it does | | --- | --- | | `morphTo(name)` | Morphs to another icon. Same as setting `name`, but it returns the Promise. | | `hide()` | Plays the hide effect. The `name` becomes empty. | | `show(name)` | Plays the show effect. | | `playAlive()` | Plays the alive animation once. | Each method takes an optional options object that overrides the attributes for that call. The Promise gives `true` when the animation ends, or when the icon changes at once with no animation (same name, or reduced motion for `morphTo`, `hide` and `show`). The Promise gives `false` in these cases: - A new `morphTo`, `hide`, `show` or `playAlive` call that starts an animation interrupts the running animation. The interrupted call resolves `false`. - `playAlive()` does not play, because the icon is empty or hidden, reduced motion is on, an animation is already running (a morph or an alive run), or the alive duration is 0 or less. ```html ``` --- Source: https://shapcn.com/docs/feel # Feel The `easing` attribute sets how a morph feels. Three presets cover most uses: `smooth`, `spring`, and `elastic`. | Preset | What it does | | --- | --- | | `smooth` | Eases in and out. It is the same curve as the default `easeInOutCubic`. It never goes past the end shape. | | `spring` | Goes past the end shape by about 16%, then settles with one wobble. | | `elastic` | Goes past the end shape by about 53%, then settles with three to five wobbles. | The three presets run on one clock below. All three icons start and end at the same time. ```html ``` ## The curves Each chart plots progress against time. The dashed line is the end shape. A curve above the dashed line goes past it. | Preset | Highest value (1.00 is the end shape) | | --- | --- | | `smooth` | 1.00 | | `spring` | 1.16 | | `elastic` | 1.53 | The engine plans the structure of a morph with the smooth timing. The order of the parts does not change with the feel. ## Use a preset Set the attribute. ```html ``` In code, pass `easing` to `planMorph`. See [JavaScript](https://shapcn.com/docs/javascript.md). --- Source: https://shapcn.com/docs/effects # Effects An effect plays when an icon appears or disappears. Set the `name` of a `` to an empty string to hide it. Set a name to show it. The `effect` attribute picks the effect. ```html ``` ```html ``` There are seven names: `implode`, `explode`, `assemble`, `draw`, `erase`, `scale`, and `expand`. Each name works in both directions. The direction picks the variant. The default is `implode` to hide and `expand` to show. The names come in pairs that play the same effect: `implode` and `expand`, `explode` and `assemble`, `draw` and `erase`. `scale` stands alone. So the seven names make four looks. In code, call `hide()` and `show(name)`. Both take an `effect` option. See [Element](https://shapcn.com/docs/element.md). Each demo below hides and shows the icon again every two seconds. ## implode The parts go into the body. The body becomes a simple shape. The shape shrinks to the center. This is the default for hiding. ```html ``` ## expand The reverse of `implode`, with an ease-out. This is the default for showing. The parts grow out of a point at the center. ```html ``` ## explode Every stroke flies away from the center, turns, and shrinks. ```html ``` ## assemble The reverse of `explode`, with an ease-out. The strokes fly in and settle into the icon. ```html ``` ## draw The strokes draw on along their own path, in source order. ```html ``` ## erase The strokes erase along their own path. ```html ``` ## scale The whole icon scales to or from zero about the center. Showing uses the `gentle` easing. ```html ``` --- Source: https://shapcn.com/docs/icons # Icons The examples that import `shape-aware-morph` use the npm package. It is in progress. See [Install](https://shapcn.com/docs/install.md). ## Lucide The script tag build has the 1,858 Lucide icons built in. Use any Lucide name. ```html ``` With the npm package, `registerLucide()` registers all the Lucide icons. It loads them on demand. To ship fewer icons, import the ones you use. Each icon family is its own module with one named export per icon (camelCase). A bundler drops the icons you do not import. ```html ``` The default export of `shape-aware-morph/lucide` is the whole map keyed by the original names (`import all from 'shape-aware-morph/lucide'`). Names that start with a digit, or that are reserved words, get a leading underscore in the export (for example `_24Hours`). ## The 12 sets All 12 sets are separate modules. The icon names inside `` are `:`, for example `tabler-outline:bell`. Lucide names have no prefix. | Import path | Style | License | | --- | --- | --- | | `shape-aware-morph/lucide` | stroke | Lucide (ISC) | | `shape-aware-morph/tabler-outline` | stroke | Tabler (MIT) | | `shape-aware-morph/tabler-filled` | fill | Tabler (MIT) | | `shape-aware-morph/hugeicons` | stroke | Hugeicons (MIT) | | `shape-aware-morph/phosphor-thin-s` | stroke | Phosphor (MIT) | | `shape-aware-morph/phosphor-light-s` | stroke | Phosphor (MIT) | | `shape-aware-morph/phosphor-regular-s` | stroke | Phosphor (MIT) | | `shape-aware-morph/phosphor-bold-s` | stroke | Phosphor (MIT) | | `shape-aware-morph/phosphor-fill` | fill | Phosphor (MIT) | | `shape-aware-morph/phosphor-duotone-s` | stroke | Phosphor (MIT) | | `shape-aware-morph/remix-line-s` | stroke | Remix (Remix Icon License 1.0) | | `shape-aware-morph/remix-fill` | fill | Remix (Remix Icon License 1.0) | ## registerFamilyIcons The sets other than Lucide use `registerFamilyIcons(family, icons)`. Pass the family id and a map of the icons you import. The names become `:`. ```html ``` The default export of a family module is the full family spec. You can pass it to `registerFamilyFile`. An unknown family id throws an error that lists the known ids. ## Your own icons `registerSVGs` reads plain SVG markup. It supports `path`, `line`, `polyline`, `polygon`, `circle`, `ellipse`, and `rect`. It scales the `viewBox` to the 24 grid. It returns the list of registered names. ```js import { registerLucide, registerSVGs, planMorph } from 'shape-aware-morph'; await registerLucide(); const names = registerSVGs({ 'my-spark': '', }, { kind: 'stroke' }); console.log(names); // ['my-spark'] const plan = planMorph('my-spark', 'heart'); // the name works like any icon name console.log(plan.frame(0.5).length > 0); // true // ``` - `kind: 'stroke'` (default): outline icons with stroke width 2. Names stay as you give them. - `kind: 'fill'`: solid icons. Names become `svg:`. Use `family: 'mine'` to choose the prefix. - A `viewBox` with a non-zero origin is not supported. Groups, transforms, and styles are ignored. - Solid shapes (`circle`, `ellipse`, `rect`, `polygon`) become paths, so they morph like path icons. - An invalid path, a `kind` other than `'stroke'` or `'fill'`, or a non-object argument throws an error. Your own icons then morph with every other icon. ```html ``` For a lower-level path, `registerIcons(map)` takes icon nodes in the Lucide format. See [JavaScript](https://shapcn.com/docs/javascript.md). --- Source: https://shapcn.com/docs/javascript # JavaScript The examples that import `shape-aware-morph` use the npm package. It is in progress. See [Install](https://shapcn.com/docs/install.md). The package exports a plan API that does not touch the DOM, and a small DOM layer. Only the functions listed on this page are stable. The package also exports internal helpers. Do not rely on them. ## planMorph `planMorph(from, to, opts?)` plans a morph from one icon to another. It returns a plan. `from` and `to` are icon names. The plan does not touch the DOM. ```js import { registerLucide, planMorph } from 'shape-aware-morph'; await registerLucide(); const plan = planMorph('mic', 'mic-off', { easing: 'spring' }); const entries = plan.frame(0.5); // drawing entries at the middle of the morph console.log(entries.length, Object.keys(entries[0])); ``` `plan.frame(t)` takes a time `t` from 0 to 1. It returns an array of drawing entries. Each entry has a path string `d`, an `opacity`, and a `widthScale`. It also has the fields `key`, `slot`, `depth`, and `occluder`. The next example draws one frame as SVG. It reads `d`, `opacity`, and `widthScale`. It ignores `occluder`, so overlapping strokes do not mask each other. `MorphIcon` draws with the masks. ```js import { registerLucide, planMorph } from 'shape-aware-morph'; await registerLucide(); const plan = planMorph('mic', 'mic-off'); const paths = plan.frame(0.5) .map((e) => ``) .join(''); const svg = `${paths}`; console.log(svg.length > 0); ``` `plan.snapshot(t)` returns the exact picture at time `t`. Pass it to `planMorph` as the start point, to continue from the middle of a morph. ### Options | Option | Default | Meaning | | --- | --- | --- | | `easing` | `easeInOutCubic` | An easing name (see [Element](/docs/element)) or a function from `t` to a number. | | `stagger` | `0` | A number from 0 to 0.99. | | `occlusion` | `none` | `none`, `mask`, or `fill`. | | `objects` | `false` | `false`, `'containers'`, `'single'`, or `true` (same as `'single'`). | The export `DEFAULTS` holds every engine knob with its default value, frozen. The export `easings` maps the easing names to functions. ## planAlive `planAlive(name, opts?)` returns a plan for the alive animation. It has the same `frame(t)` as `planMorph`, and a `kind` field. The icon looks the same at `frame(0)` and at `frame(1)`. The `kind` is one of `radiate`, `ripple`, `swing`, or `pop`. ```js import { registerLucide, planAlive } from 'shape-aware-morph'; await registerLucide(); console.log(planAlive('bell').kind); // 'swing' console.log(planAlive('sun').kind); // 'radiate' ``` ## alive `alive(el, opts?)` wires the alive animation to an element in code. It plays on pointer enter and on keyboard focus. `el` is a `MorphIcon` or a `` element. It returns a function that removes the listeners. ```html ``` The attribute `hover="alive"` does the same without code. ## defineMorphIcon `defineMorphIcon(tagName = 'morph-icon')` defines the custom element and returns its class. It returns the existing class if the tag is already defined. It returns `null` when there is no DOM, for example in Node. ```html ``` ## registerIcons `registerIcons(map)` adds icons to the registry. The icons join the Lucide family: 24 grid, stroke width 2. The map goes from a name to an icon node. An icon node is a list of `[tag, attributes]` pairs. ```html ``` ## Other exports These exports are listed in the package README and typed in `index.d.ts`. | Export | What it does | | --- | --- | | `registerLucide()` | Loads the Lucide icons into the registry. Returns a Promise of the icon map. | | `planEffect(name, direction, effect, opts?)` | Plans an appear (`'in'`) or disappear (`'out'`) effect. | | `MorphIcon` | The class behind the element. `new MorphIcon(target, opts)` works on an `` or a container. | | `getIconNames(withShapes?)` | Lists the registered icon names. | | `iconToSVG(nameOrNode, opts?)` | Returns static SVG markup of an icon. | | `registerFamilyFile(json)` | Registers a family spec. | | `registerFamilyIcons(family, icons)` | See [Icons](/docs/icons). | | `registerSVGs(svgs, opts?)` | See [Icons](/docs/icons). | | `DEFAULTS`, `easings` | The engine defaults and the easing functions. | --- Source: https://shapcn.com/docs/frameworks # Frameworks The examples that import `shape-aware-morph` use the npm package. It is in progress. See [Install](https://shapcn.com/docs/install.md). `` is a custom element, so it works in any framework. Define the element once, then use the tag. ## Plain HTML Use the script tag. Set the `name` attribute, or the `name` property, or call a method. ```html ``` ## React Define the element once, then use the tag. Use string or number values for the attributes. ```tsx import { useEffect, useState } from 'react'; import { registerLucide, defineMorphIcon } from 'shape-aware-morph'; export function MicButton() { const [on, setOn] = useState(true); useEffect(() => { registerLucide().then(() => defineMorphIcon()); }, []); return ( ); } ``` Call `defineMorphIcon` in an effect. On the server there is no DOM, and `defineMorphIcon` returns `null`. ### React types The package types add `morph-icon` to `JSX.IntrinsicElements`, to the `react` module, and to `HTMLElementTagNameMap`. Any import of `shape-aware-morph` brings them in. The attributes are typed as `name`, `size`, `color`, `stroke-width`, `duration`, `easing`, `stagger`, `effect`, and `hover`, plus `class`, `className`, `style`, `id`, `key`, and `ref`. To call the methods, type the ref with `MorphIconElement`. ```tsx import { useRef } from 'react'; import type { MorphIconElement } from 'shape-aware-morph'; export function Hide() { const ref = useRef(null); return ( <> ); } ``` ## Vue Tell Vue that `morph-icon` is a custom element. Then use the tag. ```ts // vite.config.ts import vue from '@vitejs/plugin-vue'; export default { plugins: [ vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === 'morph-icon' } } }), ], }; ``` ```vue ``` ## Svelte Svelte passes the tag through. Define the element when the component mounts. ```svelte ``` ## Custom element rules - The element reads its attributes when it connects. It then watches the nine attributes in [Element](https://shapcn.com/docs/element.md). - The element keeps its state if you move it in the DOM. It stops any running frame when it disconnects. - Define the element before or after you add the tag. A tag that exists before the definition upgrades with its attributes. --- Source: https://shapcn.com/docs/accessibility # Accessibility ## Reduced motion When the user sets `prefers-reduced-motion: reduce`, icons change at once with no animation. The attribute `hover="alive"` does nothing. In code, `morphTo`, `hide`, and `show` resolve `true` at once. `playAlive` resolves `false`. To turn this off, pass `respectReducedMotion: false` to `MorphIcon`. ```js import { MorphIcon } from 'shape-aware-morph'; const icon = new MorphIcon(document.querySelector('#slot'), { name: 'mic', respectReducedMotion: false, // always animate }); ``` The engine reads the setting each time an animation starts. Turn on reduced motion in your system settings, then reload this page. The demos here then change without motion. ## Focus The element is not focusable. It is a picture. Put it in a `button` or a link, and the control takes focus as usual. With `hover="alive"`, the alive animation plays on pointer enter and on keyboard focus. The focus handler goes on the nearest ancestor that is a `button`, `a`, `input`, `select`, or `textarea`, or that has a `tabindex` or `role="button"`. If there is none, it goes on the element. ```html ``` Press Tab to move focus to the button above. The bell swings. ## aria The element sets no aria attributes. You add them. - An icon next to a text label is decoration. Set `aria-hidden="true"` on the icon. - An icon that carries meaning alone needs a name. Set `role="img"` and `aria-label` on the element, or put an `aria-label` on the button that holds it. - A name that changes needs a label that changes with it. Update the label when you change the `name`. ```html ```