| name | framer-motion |
| description | Creates animations with Framer Motion/Motion including transitions, gestures, layout animations, and scroll effects. Use when animating React components, creating page transitions, adding gesture interactions, or building animated interfaces. |
Framer Motion
Production-ready animation library for React with declarative animations and gestures.
Quick Start
Install:
npm install framer-motion
Basic animation:
import { motion } from 'framer-motion';
function Component() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
Hello World
</motion.div>
);
}
Core Concepts
Motion Components
import { motion } from 'framer-motion';
// Any HTML element
<motion.div />
<motion.span />
<motion.button />
<motion.svg />
<motion.path />
// Custom components
const MotionCard = motion(Card);
Animate Prop
// Simple values
<motion.div animate={{ x: 100 }} />
// Multiple properties
<motion.div
animate={{
x: 100,
opacity: 0.5,
scale: 1.2,
rotate: 45,
}}
/>
// Keyframes
<motion.div
animate={{
x: [0, 100, 0],
opacity: [1, 0.5, 1],
}}
/>
Initial State
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
/>
// Disable initial animation
<motion.div initial={false} animate={{ x: 100 }} />
Exit Animations
import { motion, AnimatePresence } from 'framer-motion';
function Modal({ isOpen, onClose }) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="modal-backdrop"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
className="modal-content"
>
Modal content
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
Transitions
Basic Transitions
<motion.div
animate={{ x: 100 }}
transition={{
duration: 0.5,
delay: 0.2,
ease: 'easeInOut',
}}
/>
Spring Physics
<motion.div
animate={{ x: 100 }}
transition={{
type: 'spring',
stiffness: 100,
damping: 10,
mass: 1,
}}
/>
// Bounce effect
<motion.div
animate={{ y: 0 }}
initial={{ y: -100 }}
transition={{
type: 'spring',
bounce: 0.5,
}}
/>
Tween (Keyframe)
<motion.div
animate={{ x: 100 }}
transition={{
type: 'tween',
ease: 'easeInOut',
duration: 0.5,
}}
/>
// Custom easing
<motion.div
transition={{
ease: [0.17, 0.67, 0.83, 0.67], // Cubic bezier
}}
/>
Per-Property Transitions
<motion.div
animate={{ x: 100, opacity: 0 }}
transition={{
x: { type: 'spring', stiffness: 100 },
opacity: { duration: 0.2 },
}}
/>
Variants
Basic Variants
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
<motion.div
variants={variants}
initial="hidden"
animate="visible"
/>
Orchestration
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
},
},
};
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
function List({ items }) {
return (
<motion.ul
variants={container}
initial="hidden"
animate="visible"
>
{items.map((item) => (
<motion.li key={item.id} variants={item}>
{item.text}
</motion.li>
))}
</motion.ul>
);
}
Dynamic Variants
const variants = {
hidden: { opacity: 0 },
visible: (custom: number) => ({
opacity: 1,
transition: { delay: custom * 0.1 },
}),
};
{items.map((item, i) => (
<motion.div
key={item.id}
custom={i}
variants={variants}
initial="hidden"
animate="visible"
/>
))}
Gestures
Hover
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Click me
</motion.button>
// With variants
<motion.div
whileHover="hover"
variants={{
hover: { scale: 1.1, rotate: 5 },
}}
/>
Tap/Click
<motion.div
whileTap={{ scale: 0.9 }}
onTap={() => console.log('Tapped!')}
onTapStart={() => console.log('Tap started')}
onTapCancel={() => console.log('Tap cancelled')}
/>
Drag
<motion.div
drag
dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}
dragElastic={0.2}
dragMomentum={false}
whileDrag={{ scale: 1.1 }}
onDragStart={(event, info) => console.log('Drag started')}
onDrag={(event, info) => console.log('Dragging', info.point)}
onDragEnd={(event, info) => console.log('Drag ended')}
/>
// Constrain to parent
<motion.div ref={constraintsRef}>
<motion.div drag dragConstraints={constraintsRef} />
</motion.div>
// Drag axis
<motion.div drag="x" /> // Horizontal only
<motion.div drag="y" /> // Vertical only
Focus
<motion.input
whileFocus={{ scale: 1.02, borderColor: '#3b82f6' }}
/>
Layout Animations
Basic Layout
<motion.div layout>
{isExpanded ? 'Expanded content' : 'Collapsed'}
</motion.div>
Shared Layout
function Tabs({ tabs, activeTab, setActiveTab }) {
return (
<div className="tabs">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className="tab"
>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="underline"
className="underline"
/>
)}
</button>
))}
</div>
);
}
Layout Groups
import { LayoutGroup } from 'framer-motion';
<LayoutGroup>
<motion.div layout />
<motion.div layout />
</LayoutGroup>
Layout Transition
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: 'easeOut' },
}}
/>
Scroll Animations
Scroll-Linked Values
import { motion, useScroll, useTransform } from 'framer-motion';
function ParallaxHeader() {
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 300], [0, -100]);
const opacity = useTransform(scrollY, [0, 300], [1, 0]);
return (
<motion.header style={{ y, opacity }}>
<h1>Parallax Header</h1>
</motion.header>
);
}
Scroll Progress
function ProgressBar() {
const { scrollYProgress } = useScroll();
return (
<motion.div
className="progress-bar"
style={{ scaleX: scrollYProgress }}
/>
);
}
Scroll Container
function ScrollContainer() {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
container: containerRef,
});
return (
<div ref={containerRef} style={{ overflow: 'scroll', height: 400 }}>
{/* Content */}
</div>
);
}
In View Animation
import { motion, useInView } from 'framer-motion';
function FadeInSection({ children }) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
}
whileInView
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.5 }}
/>
Animation Controls
useAnimation
import { motion, useAnimation } from 'framer-motion';
function Component() {
const controls = useAnimation();
async function sequence() {
await controls.start({ x: 100 });
await controls.start({ y: 100 });
await controls.start({ x: 0, y: 0 });
}
return (
<motion.div animate={controls}>
<button onClick={sequence}>Start Sequence</button>
</motion.div>
);
}
Start/Stop
const controls = useAnimation();
// Start animation
controls.start({ x: 100 });
// Start with variants
controls.start('visible');
// Stop animation
controls.stop();
// Set immediately (no animation)
controls.set({ x: 0 });
SVG Animations
Path Drawing
<motion.svg viewBox="0 0 100 100">
<motion.path
d="M10 10 L90 10 L90 90 L10 90 Z"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, ease: 'easeInOut' }}
stroke="#000"
strokeWidth="2"
fill="none"
/>
</motion.svg>
Circle Progress
function CircleProgress({ progress }) {
return (
<svg viewBox="0 0 100 100">
<motion.circle
cx="50"
cy="50"
r="40"
stroke="#3b82f6"
strokeWidth="8"
fill="none"
initial={{ pathLength: 0 }}
animate={{ pathLength: progress }}
transition={{ duration: 0.5 }}
style={{
rotate: -90,
transformOrigin: '50% 50%',
}}
/>
</svg>
);
}
Page Transitions
Next.js App Router
// app/template.tsx
'use client';
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>
);
}
With AnimatePresence
'use client';
import { AnimatePresence, motion } from 'framer-motion';
import { usePathname } from 'next/navigation';
export default function PageTransition({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
return (
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
</AnimatePresence>
);
}
Performance
Reduce Motion
import { useReducedMotion } from 'framer-motion';
function Component() {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
animate={{ x: shouldReduceMotion ? 0 : 100 }}
transition={{ duration: shouldReduceMotion ? 0 : 0.5 }}
/>
);
}
Hardware Acceleration
// Use transform properties for GPU acceleration
<motion.div
animate={{
x: 100, // Good - uses transform
scale: 1.2, // Good - uses transform
opacity: 0.5, // Good - composited
}}
/>
// Avoid animating layout properties
<motion.div
animate={{
width: 200, // Avoid - triggers layout
height: 200, // Avoid - triggers layout
}}
/>
Best Practices
- Use layout for size changes - Smooth without layout thrashing
- Prefer spring for natural feel - More organic than tween
- Stagger children in lists - Better visual hierarchy
- Use variants for complex animations - Cleaner code
- Respect reduced motion - Accessibility
Common Mistakes
| Mistake | Fix |
|---|---|
| Missing AnimatePresence | Wrap exit animations |
| Animating layout properties | Use transform instead |
| Key missing in lists | Add unique keys |
| No initial state | Add initial prop |
| Heavy animations | Use will-change sparingly |
Reference Files
- references/variants.md - Complex variants
- references/gestures.md - Gesture patterns
- references/scroll.md - Scroll animations