Skip to main content
Learn how to create creative, meaningful animations that enhance your icons.

What Makes a Good Animation

A good animation should:

Enhance Meaning

The animation should reinforce or reveal the icon’s purpose, not just add motion for the sake of it.

Be Creative

Avoid generic path length animations. Think about how the icon’s elements can move, transform, or interact.

Feel Natural

Use appropriate easing, timing, and physics to make animations feel smooth and intentional.

Stay Performant

Keep animations lightweight and avoid excessive complexity that could impact performance.

Avoid Generic Animations

Don’t use simple path length animations - The basic strokeDasharray/strokeDashoffset “drawing” effect is too generic and will likely result in your PR being rejected.
// ❌ Avoid this generic pattern
const BAD_VARIANTS = {
  normal: { pathLength: 0 },
  animate: { pathLength: 1 }
};
Instead, think creatively about how the icon’s meaning can be expressed through motion.

Common Animation Patterns

Scale and Pulse

Great for icons that represent emphasis or interaction:
// Heart icon - pulses to show affection
const HeartIcon = () => {
  return (
    <motion.svg
      variants={{
        normal: { scale: 1 },
        animate: { scale: [1, 1.08, 1] },
      }}
      transition={{
        duration: 0.45,
        repeat: 2,
      }}
    >
      {/* SVG paths */}
    </motion.svg>
  );
};

Rotation and Shake

Perfect for notifications or alert icons:
// Bell icon - rings like a real bell
const SVG_VARIANTS = {
  normal: { rotate: 0 },
  animate: { rotate: [0, -10, 10, -10, 0] },
};

const BellIcon = () => (
  <motion.svg
    variants={SVG_VARIANTS}
    transition={{
      duration: 0.5,
      ease: "easeInOut",
    }}
  >
    {/* SVG paths */}
  </motion.svg>
);

Multi-Element Animations

Animate different parts of the icon independently:
// Sparkles icon - different parts animate separately
const SPARKLE_VARIANTS = {
  initial: { y: 0, fill: "none" },
  hover: {
    y: [0, -1, 0, 0],
    fill: "currentColor",
    transition: { duration: 1, bounce: 0.3 },
  },
};

const STAR_VARIANTS = {
  initial: { opacity: 1, x: 0, y: 0 },
  blink: () => ({
    opacity: [0, 1, 0, 0, 0, 0, 1],
    transition: {
      duration: 2,
      type: "spring",
      stiffness: 70,
      damping: 10,
      mass: 0.4,
    },
  }),
};

Position and Movement

Animate position to convey action:
// Send icon - moves as if being sent
const SendIcon = () => (
  <svg>
    <motion.g
      variants={{
        normal: { x: 0, y: 0, scale: 1 },
        animate: { x: 3, y: -3, scale: 0.8 },
      }}
      transition={{ duration: 0.5 }}
    >
      {/* Paper airplane paths */}
    </motion.g>
    
    {/* Trail effect that appears during animation */}
    <motion.path
      variants={{
        normal: { pathLength: 0, opacity: 0 },
        animate: { pathLength: 1, opacity: 1 },
      }}
      stroke="currentColor"
      strokeDasharray="2 2"
    />
  </svg>
);

Morphing Paths

Change the d attribute to morph shapes:
// Rocket icon - fire effect morphs
const FIRE_VARIANTS = {
  normal: {
    d: "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",
  },
  animate: {
    d: [
      "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",
      "M4.5 16.5c-1.5 1.26-3 5.5-3 5.5s4.74-1 6-2.5c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",
      // More variations...
    ],
    transition: {
      duration: 2,
      ease: [0.4, 0, 0.2, 1],
      repeat: Number.POSITIVE_INFINITY,
    },
  },
};

Complex Choreography

Combine multiple animations with delays:
// Rocket icon - floats with varying motion
const VARIANTS = {
  normal: { x: 0, y: 0 },
  animate: {
    x: [0, 0, -3, 2, -2, 1, -1, 0],
    y: [0, -3, 0, -2, -3, -1, -2, 0],
    transition: {
      duration: 6,
      ease: "easeInOut",
      repeat: Number.POSITIVE_INFINITY,
      repeatType: "reverse",
      times: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 0.9, 1],
    },
  },
};

Framer Motion Patterns

All animations in this project use Framer Motion. Here are the key patterns:

Using Controls

Every icon uses useAnimation() for programmatic control:
const controls = useAnimation();

// Trigger animations
controls.start('animate');
controls.start('normal');

Variants System

Define animation states using variants:
const VARIANTS: Variants = {
  normal: {
    // Initial/rest state
  },
  animate: {
    // Animated state
  },
};

<motion.svg
  animate={controls}
  variants={VARIANTS}
/>

Transition Options

Control timing, easing, and repetition:
transition={{
  duration: 0.5,           // Animation duration
  ease: "easeInOut",       // Easing function
  repeat: 2,               // Number of repeats
  repeatType: "reverse",   // How to repeat
  delay: 0.1,             // Delay before starting
  times: [0, 0.5, 1],     // Keyframe timing
}}

Animation Examples from the Project

Study these examples for inspiration:
  • Heart (icons/heart.tsx) - Simple scale pulse
  • Bell (icons/bell.tsx) - Rotation shake
  • Sparkles (icons/sparkles.tsx) - Multi-element with fills and blinks
  • Send (icons/send.tsx) - Position change with trail effect
  • Rocket (icons/rocket.tsx) - Complex floating motion with morphing fire
  • Zap (icons/zap.tsx) - Path drawing (used creatively, not generically)

Testing Your Animation

  1. Test on hover - Ensure smooth enter/exit transitions
  2. Test programmatically - Use the imperative handle to trigger animations
  3. Check performance - Make sure animations run at 60fps
  4. Verify responsiveness - Test at different sizes
  5. Review on different devices - Check mobile and desktop behavior

Next Steps

Build docs developers (and LLMs) love