Icon morph
Icons that morph between states: menu to close, play to pause, copy to check, and more.
pnpm dlx shadcn@latest add @uiarc/icon-morph- Icon buttons that toggle between two states, such as a hamburger menu or a play control.
- Copy and add actions that should confirm with a check in place.
- Like and notify toggles that deserve a small moment of delight.
- Use copy-button for a complete copy-to-clipboard control with its own feedback.
- Use theme-switch for a labelled light and dark toggle wired to the page theme.
- Use a plain lucide icon when the icon never changes state.
Installation
Add Icon morph with the shadcn CLI, or copy the source by hand.
pnpm dlx shadcn@latest add @uiarc/icon-morphAdds the component and its local dependencies, and installs motion. First time? Add the @uiarc registry to components.json, or use the full URL:
import { useState } from "react";import { IconMorph } from "@/components/arc/icon-morph/icon-morph"; export function MenuToggle() { const [open, setOpen] = useState(false); return ( <button type="button" aria-label="Menu" aria-expanded={open} onClick={() => setOpen(value => !value)}> <IconMorph name="menu" state={open} size={20} /> </button> );}API reference
9 parts. The first is the root.
IconMorph
One entry point for eight two-state icons that morph instead of swapping. Pick one by name and drive it with state.
nameRequired"menu" | "play" | "plus" | "copy" | "theme" | "chevron" | "bell" | "heart"–Which icon to render.statebooleanfalseThe second state: menu to close, play to pause, plus or copy to check, sun to moon, chevron turned, bell on, heart liked.sizenumber24Rendered size in px.strokeWidthnumber1.75Stroke width in the 24 unit grid....propsOmit<SVGProps<SVGSVGElement>, "ref" | "children">–Forwarded to the svg.MenuIcon
Menu to close: outer lines rotate into an X and the middle line folds away. Takes the same props as IconMorph without name.
No props.
PlayPauseIcon
Play to pause: the triangle splits into two halves that straighten into bars.
No props.
PlusCheckIcon
Plus to check: each bar swings its ends into one leg of the check.
No props.
CopyCheckIcon
Copy to check: the sheets tuck away and a check draws in.
No props.
SunMoonIcon
Sun to moon: the disc grows while a shadow carves the crescent and the rays fold in.
No props.
ChevronIcon
A chevron that turns on a spring, for disclosure and sort controls.
turnnumber180Degrees the chevron turns when state is true. 180 flips down to up; 90 turns right to down.pointing"down" | "right""down"Where the chevron points at rest.BellIcon
Notifications: turning on rings the bell with a lagging clapper; turning off strikes it through.
No props.
HeartIcon
Like: the heart fills with a pop and a ring and sparks burst once. Liked color is --icon-morph-like, default --danger.
No props.
- Every icon renders aria-hidden and focusable="false"; put it inside a button that carries the accessible name.
- Reflect the state on the button with aria-pressed, aria-expanded, or a changing label, since the icon itself says nothing.
- Lines rotate and slide, and shapes interpolate their path data, all on interruptible springs, so fast toggling reverses from wherever the icon is.
- The bell ring and heart burst are one-shot moments that replay on each turn on.
- Reduced motion makes every morph instant and skips the ring and burst.
- Icons are fixed-size SVGs on a 24 unit grid; set size per breakpoint from the parent.
- Pair with a hit area of at least 40px on touch; the icon itself adds none.
- Each icon animates a few SVG attributes with motion; many on one screen are cheap.
- SunMoonIcon and BellIcon use an SVG mask with a per-instance id from useId.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use inside icon buttons whose meaning flips between two states: menu and close, play and pause, copy and copied, like and liked.
- Import the specific icon (HeartIcon, ChevronIcon) when only one is needed; IconMorph is a name switch over the same components.
- Icons use currentColor, so color them from the parent button.
- For a whole button that changes label and state use action-swap; for theme switching with a label use theme-switch.
The full library index for assistants is at /llms.txt.