Introduction
Framer Motion is the most popular animation library for React. It provides a declarative API for complex animations — layout transitions, gestures, scroll effects, and orchestrated sequences with minimal code.
Unlike CSS animations, Framer Motion handles layout animations, shared element transitions, and physics-based springs out of the box. It also manages exit animations via AnimatePresence.
This guide covers everything from basic animations to advanced production patterns.
Key Concepts
motion Components
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
>
Hello World
</motion.div>
Variants
const container = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.1 } },
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => <motion.li key={i} variants={item}>{i}</motion.li>)}
</motion.ul>
AnimatePresence
<AnimatePresence mode="wait">
{isVisible && (
<motion.div key="modal"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}>
<Modal />
</motion.div>
)}
</AnimatePresence>
Practical Examples
1. Page Transitions
// app/template.tsx
'use client';
import { motion } from 'framer-motion';
export default function Template({ children }) {
return (
<motion.div initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ ease: 'easeInOut', duration: 0.3 }}>
{children}
</motion.div>
);
}
2. Gesture Animations
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}>
Click me
</motion.button>
3. Layout Animations
function Expandable() {
const [expanded, setExpanded] = useState(false);
return (
<motion.div layout
onClick={() => setExpanded(!expanded)}
style={{ width: expanded ? 400 : 200, height: expanded ? 300 : 100 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }} />
);
}
4. Scroll-Triggered
import { motion, useScroll, useTransform } from 'framer-motion';
function ParallaxHero() {
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 500], [0, -150]);
const opacity = useTransform(scrollY, [0, 300], [1, 0]);
return <motion.div style={{ y, opacity }}><h1>Hero</h1></motion.div>;
}
Best Practices
- ✅ Use variants for coordinated multi-element animations
- ✅ Prefer spring transitions for natural feel
- ✅ Use layout prop for DOM reflow animations
- ✅ Wrap conditionals with AnimatePresence for exit animations
- ✅ Use useReducedMotion() for accessibility
- ❌ Don't animate layout properties without the layout prop
- ❌ Don't over-animate — subtle is better
Common Pitfalls
- 🚫 Missing key prop with AnimatePresence
- 🚫 Animating expensive properties (filter, box-shadow)
- 🚫 Not respecting prefers-reduced-motion
- 🚫 Overusing bouncy springs without tuning