icons

motion-icon takes any SVG icon and draws it in or moves it on a spring. motion-icon-state morphs between two states, such as play and pause. Scroll the wall below and it draws itself again each time you return.

A wall of icons, drawn as you arrive

<motion-icon trigger="view"> draws in each time it scrolls into view, with a motion on top

Show the codeHide the code
<script type="module">
  import "motion-components/motion-icon";
</script>

<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/heart.svg" animation="draw pulse" trigger="view" once="false" delay="0"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/bell.svg" animation="draw wiggle" trigger="view" once="false" delay="0.07"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/rocket.svg" animation="draw bounce" trigger="view" once="false" delay="0.14"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/star.svg" animation="draw pop" trigger="view" once="false" delay="0.21"></motion-icon>

Every animation, on repeat

<motion-icon animation="…"> pick one motion, or put draw in front of it to run both

Show the codeHide the code
<script type="module">
  import "motion-components/motion-icon";
</script>

<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/pen-tool.svg" animation="draw" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/thumbs-up.svg" animation="pop" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/rocket.svg" animation="bounce" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/settings.svg" animation="rotate" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/bell.svg" animation="wiggle" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/heart.svg" animation="pulse" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/arrow-up.svg" animation="slide-up" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/arrow-down.svg" animation="slide-down" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/arrow-left.svg" animation="slide-left" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/arrow-right.svg" animation="slide-right" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/zap.svg" animation="draw wiggle" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/flame.svg" animation="draw pulse" trigger="loop" interval="1.4"></motion-icon>

--mc-icon-fill fills the inside of an outline icon; with draw, the fill fades in after the line

Show the codeHide the code
<script type="module">
  import "motion-components/motion-icon";
</script>

<style>
  .filled-blue { --mc-icon-fill: #bfdbfe; }
  .filled-amber { --mc-icon-fill: #fde68a; }
</style>

<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/heart.svg" class="filled-blue" animation="draw pulse" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/star.svg" class="filled-amber" animation="draw pop" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/bell.svg" class="filled-amber" animation="draw wiggle" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/cloud.svg" class="filled-blue" animation="draw bounce" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/droplet.svg" class="filled-blue" animation="draw" trigger="loop" interval="1.4"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/shield.svg" class="filled-amber" animation="pop" trigger="loop" interval="1.4"></motion-icon>

Five ways to start it

Hover this link hover follows the whole link and springs back on leave
click runs on every press of the button
view draws in when it scrolls into view
mount plays once when the page loads
loop repeats with a pause between runs

<motion-icon trigger="…"> inside a button or link, hover and click follow that button or link

Show the codeHide the code
<script type="module">
  import "motion-components/motion-icon";
</script>

<a href="/next">
  Hover this link
  <motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/arrow-right.svg" animation="bounce"></motion-icon>
</a>

<button type="button">
  <motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/refresh-cw.svg" animation="rotate" trigger="click" duration="0.9"></motion-icon>
  Click to refresh
</button>

<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/eye.svg" trigger="view" once="false"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/party-popper.svg" animation="draw pop" trigger="mount"></motion-icon>
<motion-icon src="https://cdn.jsdelivr.net/npm/lucide-static@1/icons/bell-ring.svg" animation="wiggle" trigger="loop" interval="0.8"></motion-icon>

Icons with two states. Press them.

<motion-icon-state toggle> a real toggle button: Space and Enter switch it, and it reports its state to screen readers. --mc-icon-color-active colors each icon in its second state.

Show the codeHide the code
<script type="module">
  import "motion-components/motion-icon-state";
</script>

<style>
  motion-icon-state {
    --mc-icon-color: #18181b;         /* the first state */
    --mc-icon-color-active: #a16207;  /* the second state */
  }
</style>

<motion-icon-state name="menu" toggle label="Menu"></motion-icon-state>
<motion-icon-state name="play" toggle label="Play"></motion-icon-state>
<motion-icon-state name="copy" toggle label="Copy"></motion-icon-state>
<motion-icon-state name="plus" toggle label="Expand"></motion-icon-state>
<motion-icon-state name="chevron" toggle label="Details"></motion-icon-state>
<motion-icon-state name="heart" toggle label="Like"></motion-icon-state>
<motion-icon-state name="loading" toggle label="Load"></motion-icon-state>
<motion-icon-state name="eye" toggle label="Show password"></motion-icon-state>