framer-motion
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.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
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
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-28
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!