| name | motion-framer |
| description | Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit animations, spring physics, and scroll-based effects. Use when building interactive UI components, micro-interactions, page transitions, or complex animation sequences. |
Motion & Framer Motion
Overview
Motion (formerly Framer Motion) is a production-ready animation library for React and JavaScript that enables declarative, performant animations with minimal code. It provides motion components that wrap HTML elements with animation superpowers, supports gesture recognition (hover, tap, drag, focus), and includes advanced features like layout animations, exit animations, and spring physics.
When to use this skill:
- Building interactive UI components (buttons, cards, menus)
- Creating micro-interactions and hover effects
- Implementing page transitions and route animations
- Adding scroll-based animations and parallax effects
- Animating layout changes (resizing, reordering, shared element transitions)
- Drag-and-drop interfaces
- Complex animation sequences and state-based animations
- Replacing CSS transitions with more powerful, controllable animations
Technology:
- Motion (v11+) - The modern, smaller library from Framer Motion creators
- Framer Motion - The full-featured predecessor (still widely used)
- React 18+ compatible, also supports Vue
- Supports TypeScript
- Works with Next.js, Vite, Remix, and all modern React frameworks
Core Concepts
1. Motion Components
Convert any HTML/SVG element into an animatable component by prefixing with motion.:
import { motion } from "framer-motion"
<motion.div />
<motion.button />
<motion.svg />
<motion.path />
Every motion component accepts animation props like animate, initial, transition, and gesture props like whileHover, whileTap, etc.
2. Animate Prop
The animate prop defines the target animation state. When values change, Motion automatically animates to them:
<motion.div animate={{ x: 100 }} />
<motion.div animate={{ x: 100, opacity: 1, scale: 1.2 }} />
const [isOpen, setIsOpen] = useState(false)
<motion.div animate={{ width: isOpen ? 300 : 100 }} />
3. Initial State
Set the initial state before animation using the initial prop:
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>
Set initial={false} to disable initial animations on mount.
4. Transitions
Control how animations move between states using the transition prop:
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{
x: { type: "spring", stiffness: 300 },
opacity: { duration: 0.2 }
}}
/>
Transition types:
"tween" (default) - Duration-based with easing
"spring" - Physics-based spring animation
"inertia" - Decelerating animation (used in drag)
5. Variants
Organize animation states using named variants for cleaner code and propagation to children:
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, scale: 0.9 }
}
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
/>
Variant propagation - Children automatically inherit parent variant states:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}
const itemVariants = {
hidden: { x: -20, opacity: 0 },
visible: { x: 0, opacity: 1 }
}
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
Common Patterns
1. Hover Animations
Animate on hover using whileHover prop:
<motion.button
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.2 }}
>
Hover me
</motion.button>
<motion.div
whileHover={{
scale: 1.05,
backgroundColor: "#f0f0f0",
boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)"
}}
>
Hover card
</motion.div>
<motion.button
whileHover={{
scale: 1.2,
transition: { duration: 0.1 } // Transition for gesture start
}}
transition={{ duration: 0.5 }} // Transition for gesture end
>
Button
</motion.button>
Hover with nested elements:
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>Title</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>
2. Tap/Press Animations
Animate on tap/press using whileTap prop:
<motion.button
whileTap={{ scale: 0.9 }}
>
Click me
</motion.button>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95, rotate: 3 }}
>
Interactive button
</motion.button>
const buttonVariants = {
rest: { scale: 1 },
hover: { scale: 1.1 },
pressed: { scale: 0.95 }
}
<motion.button
variants={buttonVariants}
initial="rest"
whileHover="hover"
whileTap="pressed"
>
Button
</motion.button>
3. Drag Interactions
Make elements draggable with the drag prop:
<motion.div drag />
<motion.div drag="x" />
<motion.div drag="y" />
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
/>
<motion.div ref={constraintsRef}>
<motion.div drag dragConstraints={constraintsRef} />
</motion.div>
<motion.div
drag
whileDrag={{
scale: 1.1,
boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
cursor: "grabbing"
}}
dragElastic={0.1} // Elasticity when dragging outside constraints
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
/>
Drag events:
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>
4. Exit Animations (AnimatePresence)
Animate components when they're removed from the DOM using AnimatePresence:
import { AnimatePresence } from "framer-motion"
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
Key requirements:
- Component must be direct child of
<AnimatePresence>
- Must have a unique
key prop
- Use
exit prop to define exit animation
List items with exit animations:
<AnimatePresence>
{items.map(item => (
<motion.li
key={item.id}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
layout // Smooth layout shifts
>
{item.name}
</motion.li>
))}
</AnimatePresence>
Staggered exit animations:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.1
}
},
exit: {
opacity: 0,
transition: {
when: "afterChildren",
staggerChildren: 0.05,
staggerDirection: -1
}
}
}
<AnimatePresence>
{show && (
<motion.div variants={containerVariants} initial="hidden" animate="visible" exit="exit">
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
</motion.div>
)}
</AnimatePresence>
5. Layout Animations
Automatically animate layout changes (position, size) with the layout prop:
<motion.div layout />
<motion.div layout="position" />
<motion.div layout="size" />
Grid layout animation:
const [columns, setColumns] = useState(3)
<motion.div className="grid">
{items.map(item => (
<motion.div
key={item.id}
layout
transition={{ layout: { duration: 0.3, ease: "easeInOut" } }}
/>
))}
</motion.div>
Shared layout animations (layoutId):
Connect two different elements for smooth transitions using layoutId:
<nav>
{tabs.map(tab => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="underline"
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}
/>
)}
</button>
))}
</nav>
<motion.img
src={thumbnail}
layoutId="product-image"
onClick={() => setExpanded(true)}
/>
<AnimatePresence>
{expanded && (
<motion.div layoutId="product-image">
<img src={fullsize} />
</motion.div>
)}
</AnimatePresence>
6. Scroll-Based Animations
Animate elements when they enter the viewport using whileInView:
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.8 }}
transition={{ duration: 0.5 }}
>
Animates when scrolled into view
</motion.div>
Viewport options:
once: true - Animation triggers only once
amount: 0.5 - Percentage of element visible (0-1) or "some" | "all"
margin: "-100px" - Offset viewport boundaries
Staggered scroll animations:
<motion.ul
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={{
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
},
hidden: { opacity: 0 }
}}
>
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
7. Spring Animations
Use spring physics for natural, bouncy animations:
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring" }}
/>
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 300, // Higher = faster, snappier (default: 100)
damping: 20, // Higher = less bouncy (default: 10)
mass: 1, // Higher = more inertia (default: 1)
}}
/>
<motion.div
animate={{ rotate: 90 }}
transition={{
type: "spring",
visualDuration: 0.5, // Perceived duration
bounce: 0.25 // Bounciness (0-1, default: 0.25)
}}
/>
Spring presets:
- Gentle:
stiffness: 100, damping: 20
- Wobbly:
stiffness: 200, damping: 10
- Stiff:
stiffness: 400, damping: 30
- Slow:
stiffness: 50, damping: 20
Gesture Recognition
Motion provides declarative gesture handlers:
Gesture Props
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
whileFocus={{ outline: "2px" }}
whileDrag={{ scale: 1.1 }}
whileInView={{ opacity: 1 }}
/>
Gesture Events
<motion.div
onHoverStart={(event, info) => {}}
onHoverEnd={(event, info) => {}}
onTap={(event, info) => {}}
onTapStart={(event, info) => {}}
onTapCancel={(event, info) => {}}
onDragStart={(event, info) => {}}
onDrag={(event, info) => {}}
onDragEnd={(event, info) => {}}
onViewportEnter={(entry) => {}}
onViewportLeave={(entry) => {}}
/>
Event info objects contain:
point: { x, y } - Page coordinates
offset: { x, y } - Offset from drag start
velocity: { x, y } - Drag velocity
Hooks
useAnimate
Manually control animations with the useAnimate hook:
import { useAnimate } from "framer-motion"
function Component() {
const [scope, animate] = useAnimate()
useEffect(() => {
animate([
[scope.current, { opacity: 1 }],
["li", { x: 0, opacity: 1 }, { delay: stagger(0.1) }],
[".button", { scale: 1.2 }]
])
}, [])
return (
<div ref={scope}>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button className="button">Click</button>
</div>
)
}
Animation controls:
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0
useSpring
Create spring-animated motion values:
import { useSpring } from "framer-motion"
function Component() {
const x = useSpring(0, { stiffness: 300, damping: 20 })
return (
<motion.div style={{ x }}>
<button onClick={() => x.set(100)}>Move</button>
</motion.div>
)
}
useInView
Detect when an element is in viewport:
import { useInView } from "framer-motion"
function Component() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.5 })
return (
<div ref={ref}>
{isInView ? "In view!" : "Not in view"}
</div>
)
}
Integration Patterns
With GSAP
Combine Motion for React state-based animations and GSAP for complex timelines:
import { motion } from "framer-motion"
import gsap from "gsap"
function Component() {
const boxRef = useRef()
const handleClick = () => {
const tl = gsap.timeline()
tl.to(boxRef.current, { rotation: 360, duration: 1 })
.to(boxRef.current, { scale: 1.5, duration: 0.5 })
}
return (
<motion.div
ref={boxRef}
whileHover={{ scale: 1.1 }}
onClick={handleClick}
/>
)
}
With React Three Fiber
Animate 3D objects using Motion values:
import { motion } from "framer-motion"
import { useFrame } from "@react-three/fiber"
function Box() {
const x = useMotionValue(0)
useFrame(() => {
meshRef.current.position.x = x.get()
})
return (
<>
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<motion.div
style={{ x }}
drag="x"
dragConstraints={{ left: -5, right: 5 }}
/>
</>
)
}
With Form Libraries
Animate form validation states:
import { motion, AnimatePresence } from "framer-motion"
function FormField({ error }) {
return (
<div>
<motion.input
animate={{
borderColor: error ? "#ff0000" : "#cccccc",
x: error ? [0, -10, 10, -10, 10, 0] : 0 // Shake animation
}}
transition={{ duration: 0.4 }}
/>
<AnimatePresence>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
style={{ color: "#ff0000" }}
>
{error}
</motion.p>
)}
</AnimatePresence>
</div>
)
}
Performance Optimization
1. Use Transform Properties
Transform properties (x, y, scale, rotate) are hardware-accelerated:
<motion.div animate={{ x: 100, scale: 1.2 }} />
<motion.div animate={{ left: 100, width: 200 }} />
2. Individual Transform Properties
Motion supports individual transform properties for cleaner code:
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />
3. Reduce Motion for Accessibility
Respect user preferences for reduced motion:
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}
4. Layout Animations Performance
Layout animations can be expensive. Optimize with:
<motion.div layout="position" />
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>
5. Use layoutId Sparingly
layoutId creates shared layout animations but tracks elements globally. Use only when needed.
Common Pitfalls
1. Forgetting AnimatePresence for Exit Animations
Problem: Exit animations don't work
{show && <motion.div exit={{ opacity: 0 }} />}
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
2. Missing key Prop in Lists
Problem: AnimatePresence can't track elements
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>
3. Animating Non-Transform Properties
Problem: Janky animations, poor performance
<motion.div animate={{ top: 100, left: 50, width: 200 }} />
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />
4. Overusing Layout Animations
Problem: Performance issues with many layout-animated elements
{items.map(item => <motion.div layout>{item}</motion.div>)}
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // Cheaper animation
exit={{ opacity: 0 }}
/>
))}
5. Not Using Variants for Complex Animations
Problem: Duplicated animation code, no child orchestration
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
}
<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />
6. Incorrect Transition Timing
Problem: Transitions don't apply to specific gestures
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }}
/>
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 0.2 }
}}
transition={{ duration: 0.5 }}
/>
Resources
Official Documentation
Bundled Resources
This skill includes:
references/
api_reference.md - Complete Motion API reference
variants_patterns.md - Variant patterns and orchestration
gesture_guide.md - Comprehensive gesture handling guide
scripts/
animation_generator.py - Generate Motion component boilerplate
variant_builder.py - Interactive variant configuration tool
assets/
starter_motion/ - Complete Motion + Vite starter template
examples/ - Real-world Motion component patterns
Community Resources