Beautifully animated, two-tone icon libraries with CSS-only hover animations. Currently supports Lucide (1,933 icons), Heroicons (324 icons), and Iconoir (1,383 icons). Zero JavaScript animation dependencies.
Last week (March 7-8) over the weekend I needed animated icons for VerifyWise, an AI governance platform I'm working on. Looked around but couldn't find what I wanted. Everything was either bloated with JS animation libraries, only had a few dozens of icons (one had 340+ tbh) or came with per-icon licensing. I just needed was simple CSS hover animations that would work anywhere.
So I used Claude Code to write a build system that takes Lucide's SVGs, breaks them into individual elements, figures out what each shape is, and assigns the right animation. I first built the first 1,933 icons and then added Iconoir and Heroicons. 3,640 animated icons were done over a weekend.
- CSS transition-based animations triggered on hover (no Framer Motion, no JS)
- Two-tone color support via CSS custom properties
- 3,640 animated icons across three icon sets
- Multiple output formats: React, Vue, Svelte, Solid, Web Components, standalone SVGs
- Accessible:
role="img",aria-label, and<title>on every icon - Semantic animations per category (bells ring, hearts beat, gears rotate, shields fill)
- Fully visible default state: animations only add effects on hover
- Shared animation engine: adding new icon sets requires only config + category mapping
| Icon set | Icons | Wrapper class |
|---|---|---|
| Lucide | 1,933 | al-icon-wrapper |
| Iconoir | 1,383 | ai-icon-wrapper |
| Heroicons | 324 | ah-icon-wrapper |
All packages are published under the @animated-color-icons scope:
| Icon set | React | Vue | Svelte | Solid | Web Components |
|---|---|---|---|---|---|
| Lucide | @animated-color-icons/lucide-react |
@animated-color-icons/lucide-vue |
@animated-color-icons/lucide-svelte |
@animated-color-icons/lucide-solid |
@animated-color-icons/lucide-wc |
| Heroicons | @animated-color-icons/heroicons-react |
@animated-color-icons/heroicons-vue |
@animated-color-icons/heroicons-svelte |
@animated-color-icons/heroicons-solid |
@animated-color-icons/heroicons-wc |
| Iconoir | @animated-color-icons/iconoir-react |
@animated-color-icons/iconoir-vue |
@animated-color-icons/iconoir-svelte |
@animated-color-icons/iconoir-solid |
@animated-color-icons/iconoir-wc |
Each icon set is also available in standalone SVG format with embedded CSS in dist/svg/.
# React
npm install @animated-color-icons/lucide-react
# Vue
npm install @animated-color-icons/lucide-vue
# Svelte
npm install @animated-color-icons/lucide-svelte
# Solid
npm install @animated-color-icons/lucide-solid
# Web Components
npm install @animated-color-icons/lucide-wcReplace lucide with heroicons or iconoir for other icon sets.
import { Heart, Bell, Settings } from '@animated-color-icons/lucide-react';
function App() {
return (
<div className="al-icon-wrapper">
<Heart size={24} primaryColor="#0d9488" secondaryColor="#0f766e" />
</div>
);
}Wrap the icon (or its parent) with the wrapper class to trigger animations on hover.
<template>
<div class="al-icon-wrapper">
<Heart :size="24" primary-color="#0d9488" secondary-color="#0f766e" />
</div>
</template>
<script setup>
import { Heart, Bell, Settings } from '@animated-color-icons/lucide-vue';
</script>Vue components use <style scoped> so animation CSS is automatically included per-component.
<script>
import Heart from '@animated-color-icons/lucide-svelte/Heart.svelte';
</script>
<div class="al-icon-wrapper">
<Heart size={24} primaryColor="#0d9488" secondaryColor="#0f766e" />
</div>import { Heart, Bell, Settings } from '@animated-color-icons/lucide-solid';
function App() {
return (
<div class="al-icon-wrapper">
<Heart size={24} primaryColor="#0d9488" secondaryColor="#0f766e" />
</div>
);
}<script type="module">
import '@animated-color-icons/lucide-wc/Heart.js';
</script>
<div class="al-icon-wrapper">
<animated-lucide-heart
size="24"
primary-color="#0d9488"
secondary-color="#0f766e">
</animated-lucide-heart>
</div>Web Components use Shadow DOM — each icon encapsulates its own styles. No external CSS needed, works in any framework or vanilla HTML.
Use icons directly from a CDN — no npm install, no build step:
<!-- Load the icons you need -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@animated-color-icons/lucide-wc/Heart.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@animated-color-icons/lucide-wc/Bell.js"></script>
<!-- Use them anywhere -->
<animated-lucide-heart></animated-lucide-heart>
<animated-lucide-bell size="32" primary-color="#3b82f6" secondary-color="#2563eb"></animated-lucide-bell>Replace lucide-wc with heroicons-wc or iconoir-wc for other icon sets. All packages are available on jsDelivr and unpkg.
Copy SVGs from dist/svg/ (Lucide), dist/heroicons/svg/ (Heroicons), or dist/iconoir/svg/ (Iconoir) and use them directly:
<div class="al-icon-wrapper">
<!-- paste contents of dist/svg/heart.svg -->
</div>Each SVG includes its own <style> block with all animation CSS, so no external stylesheet is needed.
Colors are controlled via CSS custom properties:
/* Lucide */
.my-icons {
--animated-lucide-primary: #0d9488;
--animated-lucide-secondary: #0f766e;
}
/* Heroicons */
.my-icons {
--animated-heroicon-primary: #3b82f6;
--animated-heroicon-secondary: #2563eb;
}
/* Iconoir */
.my-icons {
--animated-iconoir-primary: #f59e0b;
--animated-iconoir-secondary: #d97706;
}Or pass them directly to React components:
<Heart primaryColor="#3b82f6" secondaryColor="#2563eb" />| Name | Primary | Secondary |
|---|---|---|
| Teal | #0d9488 |
#0f766e |
| Blue | #3b82f6 |
#2563eb |
| Red | #ef4444 |
#dc2626 |
| Amber | #f59e0b |
#d97706 |
| Violet | #8b5cf6 |
#7c3aed |
| Pink | #ec4899 |
#db2777 |
Animations trigger on two selectors per icon set:
Lucide:
.animated-lucide-icon:hover.al-icon-wrapper:hover
Heroicons:
.animated-heroicon:hover.ah-icon-wrapper:hover
Iconoir:
.animated-iconoir:hover.ai-icon-wrapper:hover
Wrap icons in buttons, cards, or nav items and the animation triggers when hovering the container:
<button class="al-icon-wrapper">
<!-- icon animates when button is hovered -->
</button>| Animation | Effect | Used by |
|---|---|---|
fill |
Shape fills with translucent color | Shield, folder, file, user head |
fade |
Pop-in with subtle scale | Details, secondary elements |
scale-pop |
Bounce scale | Check, x, plus, eye pupil |
spin |
Full 360 rotation | Redo, refresh, loader |
gear |
Partial rotation | Settings, cog, sun, moon |
nudge |
Translate in a direction | Arrows, chevrons, truck |
shake |
Horizontal wobble | Send, cart, bars, menu, flag |
bell-ring |
Pendulum swing from top | Bell |
heart-beat |
Double-pulse scale | Heart |
mail-flap |
Envelope opens and closes | Mail, envelope |
rocket-lift |
Diagonal translate up-right | Rocket, navigation |
bar |
Grow from bottom with bounce | Bar chart, chart-bar |
handle-lift |
Lift upward | Trash lid |
page-turn |
Rotate on Y axis | Book pages |
menu-line |
Staggered scaleX | Hamburger menu (multi-element) |
pulse |
Opacity pulse | Alert indicators, signal, wifi |
dot-appear |
Pop scale on small elements | Map pin dot |
All frameworks share the same prop API:
| Prop | Type | Default | Description |
|---|---|---|---|
size |
number |
24 |
Width and height in pixels |
color |
string |
'currentColor' |
Stroke color |
primaryColor |
string |
- | Primary tone color |
secondaryColor |
string |
- | Secondary tone color |
strokeWidth |
number |
2 / 1.5 |
SVG stroke width (set-specific) |
className |
string |
'' |
Additional CSS classes |
label |
string |
icon name | Accessible label |
React components forward refs and spread additional props onto the SVG element. Web Components use kebab-case attributes (primary-color, secondary-color, stroke-width).
Browse all 3,640 icons at animated-icons.vercel.app.
# Install dependencies
npm install
# Build Lucide icons
node scripts/build.mjs
# Build Heroicons
node scripts/build-heroicons.mjs
# Build Iconoir
node scripts/build-iconoir.mjs
# Prepare gallery data (chunks + config)
node scripts/prepare-gallery.mjs
# Run the gallery locally
npm run devanimated-icons/
scripts/
animation-engine.mjs # Shared animation engine (strategies, CSS, SVG/React generation)
icon-set-configs.mjs # Central config for all icon sets
build.mjs # Lucide build (thin wrapper)
build-heroicons.mjs # Heroicons build (thin wrapper)
build-iconoir.mjs # Iconoir build (thin wrapper)
prepare-gallery.mjs # Gallery data preparation (chunks + TS config generation)
icons/
heroicons/
categories.json # Heroicons category mapping
iconoir/
categories.json # Iconoir category mapping
src/
data/
categories.json # Lucide category mapping
icon-set-config.ts # Auto-generated gallery config (from prepare-gallery)
components/ # Next.js gallery components
dist/
svg/ # Animated Lucide SVGs
react/ # Lucide React components (.jsx)
vue/ # Lucide Vue components (.vue)
svelte/ # Lucide Svelte components (.svelte)
solid/ # Lucide Solid components (.jsx)
web-components/ # Lucide Web Components (.js)
css/ # Shared Lucide CSS
heroicons/ # Same structure for Heroicons
iconoir/ # Same structure for Iconoir
- Add an entry to
scripts/icon-set-configs.mjswith paths, prefixes, and CSS variable names - Create a categories JSON file mapping icons to animation categories
- Create a thin build script (2 lines: import engine + config, call
buildIconSet) - Add a
build:<name>script topackage.json - Import the generated metadata in
src/app/page.tsx - Run
prepare-gallery.mjsto regenerate gallery config
No changes to gallery components are needed.
The shared animation engine reads source SVG icons and:
- Parses individual SVG elements (paths, circles, rects, lines)
- Classifies each element by its role (container, detail, dot)
- Assigns animation classes based on the icon's category
- Handles single-path icons with category-appropriate whole-icon animations
- Applies staggered delays (80ms increments) for sequential reveals
- Outputs SVGs with embedded
<style>blocks, plus React, Vue, Svelte, and Web Components
Animations use CSS transitions and keyframes, triggered by :hover on the icon or a parent wrapper class. No JavaScript animation library required.
We welcome contributions! There are two ways to add more animated icons:
If new icons are added to Lucide, Heroicons, or Iconoir, update the corresponding categories JSON file with the new icon-to-category mappings:
- Lucide:
src/data/categories.json - Heroicons:
icons/heroicons/categories.json - Iconoir:
icons/iconoir/categories.json
Each entry maps an icon name to a category (e.g., "heart": "status", "arrow-right": "arrows"). The category determines which animation the icon receives. See the Animation types table for available categories and their effects.
Then rebuild:
node scripts/build.mjs # Lucide
node scripts/build-heroicons.mjs # Heroicons
node scripts/build-iconoir.mjs # Iconoir
node scripts/prepare-gallery.mjs # Regenerate gallery dataThe animation engine is designed to be icon-set-agnostic. Adding a new set requires no changes to gallery components or animation logic — just config and category mapping:
- Add an entry to
scripts/icon-set-configs.mjswith paths, prefixes, and CSS variable names - Create a
categories.jsonfile mapping each icon name to an animation category - Create a thin build script (2 lines: import engine + config, call
buildIconSet) - Add a
build:<name>script topackage.json - Import the generated metadata in
src/app/page.tsx - Run
prepare-gallery.mjsto regenerate gallery config
The best icon sets for animation are those with multi-element SVGs (multiple paths, circles, rects) rather than single-path icons. Lucide (94% multi-element) and Iconoir (90%) animate particularly well.
ISC
Icons based on Lucide (ISC License), Heroicons (MIT License), and Iconoir (MIT License).