Motion.dev — React Animation Library
Motion (formerly Framer Motion) is a production-ready React animation library. Hybrid engine uses Web Animations API + ScrollTimeline for 120fps performance, with JavaScript fallback for spring physics, interruptible keyframes, and gesture tracking.
Source: motion.dev | GitHub: motiondivision/motion
When to Use
- Building UI animations (enter/exit, hover, tap, drag)
- Layout animations (reorder, resize, shared element transitions)
- Scroll-triggered and scroll-linked effects (parallax, progress)
- SVG path drawing animations
- Micro-interactions (button feedback, loading states)
- Page transitions and route animations
- Gesture-driven interactions (drag, pan, pinch)
- Complex animation orchestration (stagger, sequence)
Install
npm install motion
Import from "motion/react":
import { motion } from "motion/react"
Core API
Motion Component
Prefix any HTML/SVG tag with motion. to unlock animation props:
<motion.div animate={{ opacity: 1, scale: 1 }} />
<motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }} />
<motion.circle animate={{ pathLength: 1 }} />
Animation Props
| Prop | Purpose | Example |
|---|---|---|
animate |
Target values | { x: 100, opacity: 1 } |
initial |
Starting values | { opacity: 0, scale: 0 } |
exit |
Exit animation (with AnimatePresence) | { opacity: 0 } |
whileHover |
Hover state | { scale: 1.1 } |
whileTap |
Tap/press state | { scale: 0.95 } |
whileFocus |
Focus state | { scale: 1.05 } |
whileDrag |
Drag state | { scale: 1.1 } |
whileInView |
Scroll-triggered | { opacity: 1 } |
layout |
Auto-layout animation | true or "position" or "size" |
Transition Types
// Spring physics (default for physical properties)
<motion.div animate={{ x: 100 }} transition={{ type: "spring", stiffness: 300, damping: 30 }} />
// Tween easing (default for visual properties)
<motion.div animate={{ opacity: 1 }} transition={{ duration: 0.3, ease: "easeOut" }} />
// Keyframes
<motion.div animate={{ x: [0, 100, 0] }} transition={{ duration: 2, repeat: Infinity }} />
Gesture Animations
// Hover & tap
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
=> console.log('hover started')}
/>
// Drag
<motion.div drag dragConstraints={{ left: 0, right: 100, top: 0, bottom: 100 }} />
// Pan
<motion.div info) => console.log(info.offset)} />
// Pinch
<motion.div info) => console.log(info.offset)} />
Layout Animation
// Auto-animate layout changes
<motion.div layout />
// Shared element transition
<motion.div layoutId="underline" />
// Layout animation with spring
<motion.div layout transition={{ type: "spring", stiffness: 500, damping: 30 }} />
Scroll Animations
// Scroll-triggered
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
/>
// Scroll-linked (progress)
const { scrollYProgress } = useScroll()
<motion.div style={{ scaleX: scrollYProgress }} />
// Parallax
const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, 200])
<motion.div style={{ y }} />
Exit Animations (AnimatePresence)
<AnimatePresence>
{show && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
/>
)}
</AnimatePresence>
SVG Path Drawing
<motion.circle
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, ease: "easeInOut" }}
/>
Stagger & Orchestration
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} />)}
</motion.ul>
Performance Best Practices
- Prefer CSS properties:
transformandopacityare GPU-accelerated - Avoid animating
width/height: Usescaleorlayoutinstead - Use
layoutIdfor shared elements: Smooth transitions between components - Set
viewport={{ once: true }}: Prevent re-triggering scroll animations - Use
AnimatePresencefor exit animations: Required for exit transitions - Tree-shaking: Only import what you use
- Spring over tween for physical properties: More natural feel
Common Patterns
Page Transition
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
/>
Modal/Dialog
<AnimatePresence>
{isOpen && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="backdrop"
/>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
Staggered List
const list = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.07, delayChildren: 0.2 }
}
}
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
}
<motion.ul variants={list} initial="hidden" animate="show">
{items.map(item => (
<motion.li key={item.id} variants={item}>
{item.name}
</motion.li>
))}
</motion.ul>
Drag-to-Reorder
<motion.div
drag
dragConstraints={constraints}
dragElastic={0.2}
whileDrag={{ scale: 1.05, boxShadow: "0 5px 20px rgba(0,0,0,0.2)" }}
/>
Scroll Progress Indicator
const { scrollYProgress } = useScroll()
<motion.div
className="fixed top-0 left-0 h-1 bg-blue-500 z-50"
style={{ scaleX: scrollYProgress }}
/>
Integration with Other Skills
- ui-ux-pro-max — Use motion.dev to implement the animations recommended by ui-ux-pro-max design system
- 21st-dev — Many 21st components include motion.dev animations; customize with this skill
- tailwind-advanced — Combine motion.dev with Tailwind for animated utility-first UIs
- frontend-design — Use motion.dev for interactive prototypes and micro-interactions
- shadcn/ui — Animate shadcn/ui components with motion.dev for polished UX
When NOT to Use
- Simple CSS transitions suffice (hover color change, basic fades)
- Non-React projects (use GSAP or vanilla WAAPI instead)
- Complex timeline animations (GSAP is better for orchestration)
- Canvas/WebGL animations (use Three.js or Pixi.js)
- Pure backend logic (no UI involved)
Red Flags
- Animating width/height: Causes layout thrashing — use
scaleorlayout - Too many simultaneous springs: Performance degradation — stagger or reduce
- Missing AnimatePresence: Exit animations won't work without it
- Ignoring prefers-reduced-motion: Accessibility violation — always respect user preference
- Over-animating: Not everything needs animation — use motion with purpose
Verification
- Animations run at 60fps+ (check with Chrome DevTools Performance)
-
prefers-reduced-motionrespected - Exit animations wrapped in AnimatePresence
- Layout animations use
layoutprop (not animating width/height) - Scroll animations use
viewport={{ once: true }}where appropriate - Spring physics feel natural (not too bouncy, not too stiff)
Process
- Identify animation requirements (enter/exit, hover, scroll, layout)
- Choose animation type (spring, tween, keyframes)
- Implement with motion components
- Add gesture handlers if needed
- Test performance (60fps target)
- Verify accessibility (reduced-motion)
- Polish timing and easing
Anti-Rationalization Table
| Rationalization | Reality |
|---|---|
| "CSS transitions are enough" | Motion scales from simple to complex with one API — CSS can't do layout animations or gestures |
| "I'll add animations later" | Animations designed in from the start feel intentional; retrofitted ones feel tacked on |
| "More animation = better UX" | Purposeful animation guides attention; excessive animation distracts |
| "Spring physics everywhere" | Springs are great for physical properties; tween is better for visual properties |
| "prefers-reduced-motion is optional" | It's an accessibility requirement — always respect user preference |