| name | framer-motion |
| description | "Framer Motion React animation library skill — declarative animations, gestures, layout transitions, scroll-triggered animations, variants, AnimatePresence, and performance optimization." |
Framer Motion — Declarative React Animation
Framer Motion is a production-ready animation library for React. Simple declarative API, powerful layout animations, gestures, and server-side rendering support.
Version: 12.40.0
Package: framer-motion
License: MIT
Installation
npm install framer-motion
yarn add framer-motion
pnpm add framer-motion
Core Concepts
1. The motion Component
Every HTML/SVG element becomes animatable by prefixing with motion.:
import { motion } from "framer-motion"
<motion.div
animate={{ x: 100, opacity: 1 }}
initial={{ x: 0, opacity: 0 }}
transition={{ duration: 0.5 }}
/>
2. AnimatePresence (Exit Animations)
Wrap components that conditionally render to animate their removal:
import { AnimatePresence, motion } from "framer-motion"
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.3 }}
>
Content
</motion.div>
)}
</AnimatePresence>
3. Variants (Reusable Animation States)
Define named animation states for cleaner code:
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 }
}
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
/>
4. Gestures (Hover, Tap, Drag)
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
Click Me
</motion.button>
<motion.div
drag
dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}
whileDrag={{ scale: 1.1, boxShadow: "0 10px 30px rgba(0,0,0,0.2)" }}
/>
5. Layout Animations
Automatically animate layout changes:
<motion.div layout>
{}
</motion.div>
<motion.div layoutId="shared-element">
{selected ? <ExpandedView /> : <CompactView />}
</motion.div>
6. Scroll-Triggered Animations
import { useScroll, useTransform, motion } from "framer-motion"
function ParallaxSection() {
const { scrollYProgress } = useScroll()
const scale = useTransform(scrollYProgress, [0, 1], [0.8, 1])
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 1])
return <motion.div style={{ scale, opacity }}>Content</motion.div>
}
function ProgressBar() {
const { scrollYProgress } = useScroll()
return <motion.div style={{ scaleX: scrollYProgress, transformOrigin: "left" }} />
}
7. whileInView (Intersection Observer)
Animate elements when they enter the viewport:
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6 }}
>
Appears on scroll
</motion.div>
Common Patterns
Staggered Children
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1, delayChildren: 0.2 }
}
}
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}
<motion.ul variants={container} initial="hidden" animate="visible">
{items.map(item => (
<motion.li key={item.id} variants={item}>{item.text}</motion.li>
))}
</motion.ul>
Page Transitions (Next.js / React Router)
import { motion } from "framer-motion"
export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
)
}
Spring Physics
Spring animations feel more natural than duration-based:
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 100,
damping: 10,
mass: 1,
bounce: 0.25
}}
/>
transition={{ type: "spring", stiffness: 300, damping: 30 }}
transition={{ type: "spring", stiffness: 50, damping: 20 }}
Keyframes
<motion.div
animate={{
x: [0, 100, 50, 100, 0],
scale: [1, 1.2, 0.9, 1.1, 1],
rotate: [0, 0, 270, 270, 0]
}}
transition={{ duration: 2, repeat: Infinity }}
/>
Performance Optimization
1. Use layout="position" Instead of layout
When only position changes (not size):
<motion.div layout="position" />
2. useReducedMotion
Respect accessibility preferences:
import { useReducedMotion } from "framer-motion"
const shouldReduceMotion = useReducedMotion()
<motion.div
animate={shouldReduceMotion ? {} : { x: 100 }}
transition={{ duration: 0.5 }}
/>
3. Hardware-Accelerated Properties Only
Animate only transform and opacity for 60fps:
- ✅
x, y, scale, rotate, opacity
- ❌
width, height, top, left, backgroundColor
4. Lazy Motion (Tree Shaking)
import { m } from "framer-motion"
<m.div animate={{ opacity: 1 }} />
5. will-change Hint
<motion.div style={{ willChange: "transform" }} />
Best Practices
- Wrap all animations in
<AnimatePresence> for proper exit animations
- Use variants for reusable animation states across components
- Prefer spring over duration-based transitions for natural feel
- Set
viewport={{ once: true }} on scroll animations to avoid re-triggering
- Always provide
exit prop when using conditional rendering
- Test with
prefers-reduced-motion for accessibility
- Use
layoutId for shared element transitions between pages
- Keep animations between 150-400ms for UI feedback
Debugging
<motion.div layout layoutRoot />
<motion.div
onAnimationStart={() => console.log("started")}
onAnimationComplete={() => console.log("completed")}
/>
Common Gotchas
| Issue | Fix |
|---|
| Layout animation jitter | Add layoutId to animating elements |
| Exit animation not playing | Wrap with <AnimatePresence> |
| Children not staggering | Put staggerChildren on container's transition |
| Transform origin off | Set style={{ originX: 0.5, originY: 0.5 }} |
| Hover state flickering | Use whileHover not CSS :hover |