The Problem with Current Solutions
Animating icons in React today requires too much boilerplate and complexity.
With Framer Motion
import { motion } from 'framer-motion' ;
import { Bell } from 'lucide-react' ;
function NotificationIcon () {
return (
< motion.div
initial = { { opacity: 0 , y: 20 } }
animate = { { opacity: 1 , y: 0 , rotate: 360 } }
transition = { { duration: 1 , ease: "easeInOut" } }
whileHover = { { scale: 1.1 } }
whileTap = { { scale: 0.95 } }
>
< Bell size = { 24 } />
</ motion.div >
);
}
Problems:
π΄ Wrapper div required for every icon
π΄ Need to remember animation curves
π΄ Juggle initial/animate/transition props
π΄ Manual hover/tap state handling
π΄ Repetitive code for each icon
π΄ Adds ~30KB to bundle
With Custom CSS
import { Bell } from 'lucide-react' ;
import './animations.css' ;
function NotificationIcon () {
return (
< div className = "icon-container" >
< Bell size = { 24 } className = "animated-icon" />
</ div >
);
}
/* animations.css */
@keyframes fadeInSpin {
from {
opacity : 0 ;
transform : translateY ( 20 px ) rotate ( 0 deg );
}
to {
opacity : 1 ;
transform : translateY ( 0 ) rotate ( 360 deg );
}
}
.animated-icon {
animation : fadeInSpin 1 s ease-in-out ;
}
.icon-container:hover .animated-icon {
transform : scale ( 1.1 );
transition : transform 0.2 s ;
}
Problems:
π΄ Separate CSS file to maintain
π΄ Manual keyframe definitions
π΄ No TypeScript support
π΄ Hard to customize per instance
π΄ Difficult to trigger programmatically
The Motion Icons Solution
import { MotionIcon } from 'motion-icons-react' ;
function NotificationIcon () {
return (
< MotionIcon
name = "Bell"
animation = "spin"
entrance = "fadeInUp"
trigger = "hover"
interactive
/>
);
}
Benefits:
β
One line, no wrappers
β
15+ preset animations
β
Declarative API
β
Full TypeScript support
β
Interactive states built-in
β
Minimal bundle impact (~5KB)
Side-by-Side Comparison
Loading Spinner
Framer Motion (12 lines)
Motion Icons (1 line)
import { motion } from 'framer-motion' ;
import { Loader2 } from 'lucide-react' ;
function LoadingSpinner () {
return (
< motion.div
animate = { { rotate: 360 } }
transition = { {
duration: 1 ,
repeat: Infinity ,
ease: "linear"
} }
>
< Loader2 size = { 20 } />
</ motion.div >
);
}
Framer Motion (20 lines)
Motion Icons (8 lines)
import { motion } from 'framer-motion' ;
import { Heart } from 'lucide-react' ;
import { useState } from 'react' ;
function LikeButton () {
const [ isLiked , setIsLiked ] = useState ( false );
return (
< motion.button
onClick = { () => setIsLiked ( ! isLiked ) }
whileHover = { { scale: 1.1 } }
whileTap = { { scale: 0.95 } }
>
< motion.div
animate = { isLiked ? { scale: [ 1 , 1.2 , 1 ] } : {} }
transition = { { duration: 0.3 } }
>
< Heart size = { 24 } fill = { isLiked ? "red" : "none" } />
</ motion.div >
</ motion.button >
);
}
Entrance Animation
Framer Motion (10 lines)
Motion Icons (1 line)
import { motion } from 'framer-motion' ;
import { Star } from 'lucide-react' ;
function StarIcon () {
return (
< motion.div
initial = { { opacity: 0 , scale: 0 } }
animate = { { opacity: 1 , scale: 1 } }
transition = { { duration: 0.5 , type: "spring" } }
>
< Star size = { 32 } />
</ motion.div >
);
}
When to Use What
Use Motion Icons React when:
β
You need animated icons quickly
β
You want preset, production-ready animations
β
You prefer declarative APIs
β
You want minimal bundle size
β
You need TypeScript support
Use Framer Motion when:
β
You need complex, custom animations
β
Youβre animating entire layouts
β
You need gesture controls (drag, pan, etc.)
β
You want physics-based animations
β
Youβre already using framer-motion
Use Custom CSS when:
β
You have very specific animation needs
β
You want zero JavaScript overhead
β
Youβre comfortable writing keyframes
β
You donβt need dynamic control
Bundle Size Comparison
Library Size (minified + gzipped)
Motion Icons React ~5KB Framer Motion ~30KB Custom CSS ~1KB (but more maintenance)
Motion Icons React is built on top of Lucide React, which youβre likely already using. The additional cost is minimal.
Developer Experience
Motion Icons React
// Autocomplete works perfectly
< MotionIcon
name = "Heart" // β
Autocomplete all 3500+ icons
animation = "pulse" // β
Autocomplete all animations
trigger = "hover" // β
Autocomplete all triggers
/>
Framer Motion
// No autocomplete for animation values
< motion.div
animate = { { rotate: 360 } } // β No autocomplete
transition = { { ease: "easeInOut" } } // β Need to remember curves
/>
Real-World Usage
Motion Icons React is perfect for:
π― Loading indicators
π― Button hover effects
π― Notification badges
π― Status indicators
π― Navigation icons
π― Form feedback
π― Social reactions
Framer Motion is better for:
π― Page transitions
π― Complex layout animations
π― Drag and drop interfaces
π― Gesture-based interactions
π― Physics simulations
Conclusion
Motion Icons React isnβt trying to replace framer-motion. Itβs solving a specific problem: making icon animations dead simple .
If you need complex animations, use framer-motion. If you just want your icons to look alive without the complexity, use Motion Icons React.
TL;DR: Less code. Less complexity. Better DX.