Skip to main content

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(20px) rotate(0deg);
  }
  to {
    opacity: 1;
    transform: translateY(0) rotate(360deg);
  }
}

.animated-icon {
  animation: fadeInSpin 1s ease-in-out;
}

.icon-container:hover .animated-icon {
  transform: scale(1.1);
  transition: transform 0.2s;
}
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

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>
  );
}

Interactive Heart Button

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

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

LibrarySize (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.