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.
How this started
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.
Features
- 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
Supported icon sets
| Icon set | Icons | Wrapper class |
|---|---|---|
| Lucide | 1,933 | al-icon-wrapper |
| Iconoir | 1,383 | ai-icon-wrapper |
| Heroicons | 324 | ah-icon-wrapper |
npm packages
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 |
Output formats
Each icon set is also available in standalone SVG format with embedded CSS in dist/svg/.
Quick start
Installation
# 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-wc
Replace lucide with heroicons or iconoir for other icon sets.
React
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.
Vue
<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.
Svelte
<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>
Solid
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> ); }
Web Components
<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.
CDN (zero install)
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.
SVG
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.
Customizing colors
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" />
Color presets
| Name | Primary | Secondary |
|---|---|---|
| Teal | #0d9488 |
#0f766e |
| Blue | #3b82f6 |
#2563eb |
| Red | #ef4444 |
#dc2626 |
| Amber | #f59e0b |
#d97706 |
| Violet | #8b5cf6 |
#7c3aed |
| Pink | #ec4899 |
#db2777 |
Hover trigger
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 types
| 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 |
Component props
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).
Available icons
Browse all 3,640 icons at animated-icons.vercel.app.
Building from source
# 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 dev
Project structure
animated-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
Adding a new icon set
- 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.
How it works
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.
Contributing icons
We welcome contributions! There are two ways to add more animated icons:
Add icons to an existing set
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 data
Add an entirely new icon set
The 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.
License
ISC
Icons based on Lucide (ISC License), Heroicons (MIT License), and Iconoir (MIT License).