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-motionimport { 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
}}
/>| Stiffness | Damping | Result |
|---|---|---|
| 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
layoutprop — do not manually track positions - Always wrap conditional renders in
AnimatePresencefor exit animations - Do not animate everything — reserve motion for meaningful state changes