Framer Motion (Motion)
Production-grade animation library for React — animate, variants, AnimatePresence, layout animations, gestures, scroll-triggered effects, useMotionValue, and stagger orchestration.
Framer Motion (now branded as **Motion**) treats animation as a declarative UI concern. Instead of imperative timelines, you describe the desired state and let the library interpolate. Every `motion.*` component is a drop-in replacement for its HTML/SVG counterpart, gaining physics-based animation, gesture recognition, and layout projection for free.
## Key Points
- **Use `layout` sparingly.** Layout projection is powerful but expensive. Apply it only to elements that actually change size/position.
- **Prefer spring transitions.** Springs feel more natural than duration-based easing. Start with `stiffness: 300, damping: 24` and tune from there.
- **Keep `AnimatePresence` close to the conditional.** Wrapping too much of the tree defeats its purpose and can cause flicker.
- **Avoid animating `width`/`height` directly.** Use `layout` or `scale` transforms for better performance since transforms avoid layout thrashing.
- **Use `useMotionValue` for high-frequency updates** like drag or scroll-linked effects to bypass React re-renders.
- **Set `initial={false}` when you do not want the mount animation** to play on first render (e.g., tab switching where the first tab should appear instantly).
- **Use `mode="wait"` on AnimatePresence** when you need the exiting component to leave before the entering one mounts.
- **Animating layout properties via `animate` instead of `layout`.** Directly animating `width` or `left` causes layout recalculations on every frame.
- **Nesting multiple `AnimatePresence` without keys.** Each conditional child inside `AnimatePresence` needs a unique `key` or exit animations silently fail.
- **Creating motion values inside render.** `useMotionValue` must be called at the top level of a component, never inside loops or conditions.
- **Over-animating.** Adding transitions to every element degrades perceived performance. Animate only what draws attention or conveys state change.
- **Using `animate` prop with objects on every render.** This creates a new object reference each render, restarting the animation. Use variants or memoize the object.
## Quick Example
```bash
npm install motion
```
```tsx
// React 18+ — the package re-exports as "motion"
import { motion, AnimatePresence } from "motion/react";
```skilldb get animation-services-skills/Framer Motion (Motion)Full skill: 249 linesFramer Motion (Motion)
Core Philosophy
Framer Motion (now branded as Motion) treats animation as a declarative UI concern. Instead of imperative timelines, you describe the desired state and let the library interpolate. Every motion.* component is a drop-in replacement for its HTML/SVG counterpart, gaining physics-based animation, gesture recognition, and layout projection for free.
Key mental model: animate to a target, not through a sequence. Variants let you propagate animation intent through component trees without prop drilling.
Setup
npm install motion
// React 18+ — the package re-exports as "motion"
import { motion, AnimatePresence } from "motion/react";
For projects still on the legacy package:
npm install framer-motion
import { motion, AnimatePresence } from "framer-motion";
Minimal Example
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
>
{children}
</motion.div>
);
}
Key Techniques
Variants and Orchestration
Variants decouple animation definitions from JSX. Parent variants automatically propagate to children, enabling stagger with zero wiring.
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } },
};
export function StaggeredList({ items }: { items: string[] }) {
return (
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item} variants={itemVariants}>
{item}
</motion.li>
))}
</motion.ul>
);
}
AnimatePresence — Exit Animations
React unmounts elements immediately. AnimatePresence defers unmount until the exit animation completes.
export function Modal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
key="backdrop"
className="fixed inset-0 bg-black/40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
<motion.div
key="panel"
className="mx-auto mt-24 max-w-md rounded-xl bg-white p-6"
initial={{ scale: 0.92, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.92, opacity: 0 }}
transition={{ type: "spring", stiffness: 400, damping: 28 }}
onClick={(e) => e.stopPropagation()}
>
{/* content */}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
Layout Animations
Adding layout to a motion component enables automatic FLIP animations when its size or position changes in the DOM.
export function ExpandableCard({ expanded, toggle }: { expanded: boolean; toggle: () => void }) {
return (
<motion.div
layout
onClick={toggle}
className={expanded ? "w-full h-64" : "w-48 h-32"}
transition={{ type: "spring", stiffness: 350, damping: 30 }}
>
<motion.h2 layout="position">Title</motion.h2>
<AnimatePresence>
{expanded && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
Expanded content here.
</motion.p>
)}
</AnimatePresence>
</motion.div>
);
}
Gestures
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
Press me
</motion.button>
Scroll-Triggered Animations
import { motion, useScroll, useTransform } from "motion/react";
export function ParallaxHero() {
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
return (
<motion.div style={{ y, opacity }} className="h-screen">
<h1>Parallax Hero</h1>
</motion.div>
);
}
useMotionValue and useTransform
useMotionValue creates a value that updates outside React's render cycle for high-performance animations.
import { motion, useMotionValue, useTransform } from "motion/react";
export function Slider() {
const x = useMotionValue(0);
const background = useTransform(x, [-100, 0, 100], ["#ff0000", "#ffffff", "#00ff00"]);
return (
<motion.div style={{ background }} className="p-8">
<motion.div
drag="x"
dragConstraints={{ left: -100, right: 100 }}
style={{ x }}
className="h-12 w-12 rounded-full bg-gray-800 cursor-grab"
/>
</motion.div>
);
}
Stagger with Custom Delay
const stagger = (index: number) => ({
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: { delay: index * 0.06 },
},
});
export function CustomStagger({ items }: { items: string[] }) {
return (
<div>
{items.map((item, i) => (
<motion.div key={item} variants={stagger(i)} initial="hidden" animate="visible">
{item}
</motion.div>
))}
</div>
);
}
Best Practices
- Use
layoutsparingly. Layout projection is powerful but expensive. Apply it only to elements that actually change size/position. - Prefer spring transitions. Springs feel more natural than duration-based easing. Start with
stiffness: 300, damping: 24and tune from there. - Keep
AnimatePresenceclose to the conditional. Wrapping too much of the tree defeats its purpose and can cause flicker. - Avoid animating
width/heightdirectly. Uselayoutorscaletransforms for better performance since transforms avoid layout thrashing. - Use
useMotionValuefor high-frequency updates like drag or scroll-linked effects to bypass React re-renders. - Set
initial={false}when you do not want the mount animation to play on first render (e.g., tab switching where the first tab should appear instantly). - Use
mode="wait"on AnimatePresence when you need the exiting component to leave before the entering one mounts.
Anti-Patterns
- Animating layout properties via
animateinstead oflayout. Directly animatingwidthorleftcauses layout recalculations on every frame. - Nesting multiple
AnimatePresencewithout keys. Each conditional child insideAnimatePresenceneeds a uniquekeyor exit animations silently fail. - Creating motion values inside render.
useMotionValuemust be called at the top level of a component, never inside loops or conditions. - Over-animating. Adding transitions to every element degrades perceived performance. Animate only what draws attention or conveys state change.
- Using
animateprop with objects on every render. This creates a new object reference each render, restarting the animation. Use variants or memoize the object. - Ignoring
will-change. For complex animations, addwill-change: transformvia style to hint the browser to promote the element to its own compositing layer.
Install this skill directly: skilldb add animation-services-skills
Related Skills
Anime.js
Lightweight JavaScript animation engine — DOM, CSS, SVG, and object property animations with timeline sequencing, staggering, and spring-based easing.
Auto Animate
Zero-config animation library by FormKit — automatic transitions for DOM additions, removals, and reordering with a single function call or directive.
GSAP
GreenSock Animation Platform — timeline orchestration, ScrollTrigger, tweens, stagger, React integration with useGSAP, SplitText, morphSVG, and from/to/fromTo patterns.
Lottie
Render After Effects animations on the web with lottie-react and lottie-web — player controls, interactivity, lazy loading, and the light player for optimized bundles.
Motion One
Lightweight Web Animations API wrapper — animate, timeline, spring physics, scroll-linked animations, stagger, and minimal bundle size for modern browsers.
Popmotion
Functional animation library — spring physics, decay, keyframe interpolation, and composable animation primitives that power Framer Motion's internals.