Skip to main content

Framer Motion in React

Framer Motion is the most popular animation library for React. It provides a declarative API that makes complex animations simple — layout animations, gesture handling, exit animations, and orchestrated sequences all work out of the box.

Getting Started

Install Framer Motion and import the motion component:

npm install framer-motion
import { motion } from 'framer-motion';

function FadeIn() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      <h1>Hello World</h1>
    </motion.div>
  );
}

The motion.div component is a drop-in replacement for a regular <div>. It accepts three key props:

  • initial — the starting state when the component mounts
  • animate — the target state to animate toward
  • transition — how the animation behaves (duration, easing, delay)

Variants

Variants let you define named animation states and coordinate them across parent and child components:

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.08,
    },
  },
};

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 },
};

function StaggeredList({ items }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="show">
      {items.map((text) => (
        <motion.li key={text} variants={item}>
          {text}
        </motion.li>
      ))}
    </motion.ul>
  );
}

When the parent transitions from hidden to show, Framer Motion automatically propagates the state change to children. The staggerChildren property creates a cascading delay between each child.

Exit Animations with AnimatePresence

React removes components from the DOM immediately. AnimatePresence lets you animate components out before they are removed:

import { motion, AnimatePresence } from 'framer-motion';

function Notification({ message, isVisible }) {
  return (
    <AnimatePresence>
      {isVisible && (
        <motion.div
          key="notification"
          initial={{ opacity: 0, x: 100 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 100 }}
          transition={{ duration: 0.2 }}
          className="notification"
        >
          {message}
        </motion.div>
      )}
    </AnimatePresence>
  );
}

The exit prop defines what happens when the component is removed. AnimatePresence keeps the component in the DOM long enough for the exit animation to complete.

Layout Animations

One of Framer Motion's most powerful features is automatic layout animation. Add the layout prop and elements smoothly animate between layout changes:

function ExpandableCard({ isExpanded, onClick }) {
  return (
    <motion.div
      layout
      onClick={onClick}
      style={{
        width: isExpanded ? 400 : 200,
        height: isExpanded ? 300 : 100,
        borderRadius: 12,
        background: '#2563eb',
      }}
      transition={{ type: 'spring', stiffness: 300, damping: 30 }}
    >
      <motion.h3 layout="position">Card Title</motion.h3>
    </motion.div>
  );
}

The layout prop tells Framer Motion to automatically animate any changes to the element's size or position in the DOM. The layout="position" on the heading means only its position animates, not its size — preventing text distortion.

Spring Physics

Framer Motion uses spring animations by default, which feel more natural than duration-based animations:

<motion.div
  animate={{ x: 100 }}
  transition={{
    type: 'spring',
    stiffness: 300,  // How stiff the spring is
    damping: 20,     // How much friction (higher = less bounce)
    mass: 1,         // Weight of the object
  }}
/>
StiffnessDampingResult
High (400+)High (30+)Snappy, minimal bounce
High (400+)Low (10)Bouncy, energetic
Low (100)High (30)Slow, smooth glide
Low (100)Low (10)Slow, wobbly

For most UI elements, use stiffness: 300, damping: 25-30 for a snappy feel with minimal overshoot.

Gesture Animations

Framer Motion provides props for hover, tap, drag, and more:

<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  transition={{ type: 'spring', stiffness: 400, damping: 20 }}
  className="px-6 py-3 bg-blue-600 text-white rounded-lg"
>
  Click Me
</motion.button>

For draggable elements:

<motion.div
  drag
  dragConstraints={{ top: 0, left: 0, right: 300, bottom: 300 }}
  dragElastic={0.2}
  whileDrag={{ scale: 1.1, boxShadow: '0 10px 30px rgba(0,0,0,0.2)' }}
  className="w-20 h-20 bg-blue-500 rounded-lg cursor-grab"
/>

The dragConstraints prop limits how far the element can be dragged, and dragElastic controls how much it resists being pulled past the constraints.

Respecting Reduced Motion

Always check the user's motion preference:

import { useReducedMotion } from 'framer-motion';

function AnimatedComponent() {
  const prefersReducedMotion = useReducedMotion();

  return (
    <motion.div
      initial={prefersReducedMotion ? false : { opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
    >
      Content
    </motion.div>
  );
}

Setting initial to false skips the initial animation entirely, and a duration of 0 makes state changes instant.

Best Practices

  • Keep animations fast — 200-300ms for most UI transitions
  • Use springs for interactive elements — they respond naturally to interruption
  • Animate layout with the layout prop — do not manually track positions
  • Always wrap conditional renders in AnimatePresence for exit animations
  • Do not animate everything — reserve motion for meaningful state changes