← All articles

motion-components 1.1: Animate Any SVG Icon With One HTML Tag

Version 1.1 of motion-components adds a new category: Icons. It has two elements. <motion-icon> animates any SVG icon you already use. <motion-icon-state> morphs between two states, such as menu and close, or play and pause. Both use spring physics, and both can be interrupted at any moment.

You can try them now on the home page. Click any icon in the icon grid to see a preview and copy its code.

One line per icon

This is a complete page. Save it as index.html and open it in a browser. You do not need to install anything or run a build step.

Hover the heart and its outline draws in, line by line. Hover the bell and it wiggles.

Four ways to add an icon

WayUse it whenExample
src with a CDN URLYou want one line of HTML and no files to manage.<motion-icon src="https://cdn.jsdelivr.net/…/heart.svg">
src with your own folderYou keep your icons as files in your site, or you drew your own.<motion-icon src="icons/heart.svg">
Inline SVGYou copied the SVG from an icon website.<motion-icon><svg>…</svg></motion-icon>
iconYou use a bundler and want the icon inside your bundle.el.icon = Heart

The src attribute loads each URL once. If ten icons use the same URL, the browser downloads it one time. If the URL fails, the element fires an error event.

Icons from your own folder

src also takes a path to a file in your site. Put the SVG files in a folder, for example icons/:

This works for icons from any set and for icons you drew yourself. A stroke icon can use draw, like every other icon.

A path without a leading slash, such as icons/heart.svg, is relative to the page. For pages in subfolders, write /icons/heart.svg, so the path always starts at the root of your site.

Inline SVG

Inline SVG works well when you want the icon in your HTML with no extra request:

From an npm package

With a bundler such as Vite, import the icon as a string. lucide-static exports every icon by name, so only the icons you import end up in your bundle:

Vite can also import any SVG file as a string with the ?raw suffix. This works for every icon package that ships SVG files:

Works with the icon library you already use

Every library in this table publishes its icons as SVG files on the jsDelivr CDN. Find an icon on the library’s website and put its name at the end of the URL.

LibraryURL after https://cdn.jsdelivr.net/npm/draw
Lucidelucide-static@1/icons/{name}.svgYes
Tabler@tabler/icons@3/icons/outline/{name}.svgYes
Heroiconsheroicons@2/24/outline/{name}.svgYes
Iconoiriconoir@7/icons/regular/{name}.svgYes
Featherfeather-icons@4/dist/icons/{name}.svgYes
Phosphor@phosphor-icons/core@2/assets/regular/{name}.svgPops instead
Bootstrap Iconsbootstrap-icons@1/icons/{name}.svgPops instead
Material Symbols@material-symbols/svg-400@0/outlined/{name}.svgPops instead
Font Awesome Free@fortawesome/fontawesome-free@7/svgs/regular/{name}.svgPops instead
Simple Iconssimple-icons@15/icons/{name}.svgPops instead

The draw animation traces the lines of an icon, so it needs an outline icon. Filled icons have no lines to trace. For those, draw changes to pop by itself, so nothing breaks.

Some icon sets do not set a color in their SVG files. Material Symbols and Simple Icons are two examples. A plain <img> shows those icons in black. motion-icon gives them the current text color, the same as every other icon.

Six animations, five triggers

animation decides how the icon moves:

AnimationWhat it does
drawDraws the lines in, one after another. This is the default.
popScales up from small on a spring.
bounceDrops in from above and bounces.
rotateTurns half a circle and settles.
wiggleShakes from side to side, like a ringing bell.
pulseGrows and shrinks once, like a heartbeat.

trigger decides when it moves:

TriggerWhen the animation runs
hoverWhen the pointer enters the icon. This is the default.
clickWhen the icon is clicked.
viewWhen the icon scrolls into view.
mountWhen the page loads.
loopAgain and again, with a pause set by interval.

Icons inside buttons

Most icons on a website sit inside a button or a link. So when motion-icon is inside a <button> or an <a>, it listens to that button or link. The icon animates when the pointer enters any part of the button, including the padding and the text.

The “Read the docs” and “Star on GitHub” buttons at the bottom of the home page work this way.

Color, fill and background

Three CSS custom properties control how an icon looks:

PropertyWhat it changesDefault
--mc-icon-colorThe color of the linesThe text color
--mc-icon-fillThe color inside an outline iconNo fill
--mc-icon-sizeThe width and height1.5em

--mc-icon-fill turns an outline icon into a filled one. Add 33 to the end of a hex color for a light tint at 20% opacity. With draw, the fill fades in as the lines finish.

Icons with two states

<motion-icon-state> is for icons that show a state. Set name to choose the icon. Set active to show the second state.

nameWithout activeWith active
menumenuclose
playplaypause
copycopycheck
plusplusminus
chevronchevron downchevron up
heartempty heartfilled heart
loadingspinnercheck
eyeeyeeye with a line through it

There are two ways to switch the state. In the first way, the icon is the button. Add toggle and a label. The icon then switches itself on a click, or on the Space or Enter key:

In the second way, your own button controls the icon. This menu button changes to a close icon when it opens:

The state changes on a spring. If you click again in the middle of a change, the icon turns around from where it is. It does not jump to the end first.

State Icons: Menu to Close, Play to Pause, on a Spring explains this element in detail. It has complete recipes for a copy button, a save button with a spinner, a show password button and an accordion.

React and Vue

Both elements work in every framework. React 19 passes properties to custom elements directly, so active takes a boolean from state:

You can also wrap the icon component from your icon library. Any component that renders an <svg> works inside <motion-icon>:

In Vue, tell the compiler that motion-* tags are custom elements. The framework setup guide shows the configuration.

Safe by default

An SVG file can contain more than drawing instructions. It can contain scripts, links and references to other files. Before motion-icon shows an icon from src or icon, it keeps only the drawing elements, such as <path>, <circle> and gradients. It removes everything else: scripts, styles, images, links, event handlers and references to other files.

Accessibility

ElementDefaultHow to give it a name
motion-iconDecorative and hidden from screen readersAdd label. The icon then gets role="img".
motion-icon-state with toggleA button with aria-pressedAdd label.
motion-icon-state without toggleDecorativeGive the surrounding button a name.

When a user turns on reduced motion in their system settings, both elements skip the animation. They show the final state at once, and the loading spinner stands still.

Get started

Read the motion-icon docs and the motion-icon-state docs. Every code example on those pages works when you paste it into a page.