| name | motion |
| description | Implements React UI animation with Motion (motion/react, formerly Framer Motion): layout, AnimatePresence exits, layoutId, gestures, useScroll, and reduced-motion. Use when animating React components, shared-element transitions, or scroll-linked effects. Not for Remotion frame timelines (remotion-video), GSAP/Lottie/Rive authoring, or Three.js scenes. |
| version | 1.0.1 |
Motion (formerly Framer Motion) Animation for React
Overview
Motion is the production-ready animation library for React (formerly Framer Motion). It provides a declarative API where animations are defined as props rather than imperative code. Use it for fluid animations, layout transitions, scroll-linked effects, gesture interactions, shared layout animations, and exit animations.
Package: motion — import from "motion/react" (not "framer-motion").
Official docs: https://motion.dev/docs/react
When to Use
Use this skill when the user asks for any of the following:
- Mount/entrance animations — fade-in, slide-in, scale-in on component mount
- Exit animations — elements animating out before unmount (requires
AnimatePresence)
- Layout animations — automatic size/position transitions when layout changes
- Shared layout animations — elements that visually travel between components (tab indicators, expanding cards)
- Scroll-linked effects — parallax, scroll-triggered reveals, progress bars
- Gesture interactions —
whileHover, whileTap, whileDrag, whileFocus
- Staggered children — sequenced list or grid entrance animations
- Motion accessibility — respecting
prefers-reduced-motion, designing reduced-motion variants
- Motion system design — duration/easing tokens, spring tuning, interruption rules
Trigger keywords: motion, framer-motion, animation, animate, whileInView, AnimatePresence, layoutId, useScroll, useTransform, stagger, reduced-motion, parallax, spring, transition, exit animation, layout animation.
Prerequisites
- Node.js installed and a React project (Next.js, Vite, CRA, or similar).
- Install the package:
npm install motion
- Verify installation on Windows (PowerShell):
npm ls motion
Expected output shows motion@x.x.x with no UNMET DEPENDENCY errors.
- Import path is always
"motion/react":
import { motion, AnimatePresence, useScroll, useTransform } from "motion/react";
Do not import from "framer-motion" in new projects. The package was renamed to motion. Legacy "framer-motion" imports still work for backward compatibility but are not recommended for new code.
Procedure
1. Basic Mount Animation
Animate a component from an initial state to an animate target on mount.
import { motion } from "motion/react";
function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
{children}
</motion.div>
);
}
initial — starting state before first render.
animate — target state; Motion animates to this on mount and whenever it changes.
transition — timing config: duration + ease, or type: "spring" with stiffness/damping.
2. Hover and Tap Gestures
function InteractiveCard({ title }: { title: string }) {
return (
<motion.div
className="card"
whileHover={{ scale: 1.05, boxShadow: "0 10px 30px rgba(0,0,0,0.12)" }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
<h3>{title}</h3>
</motion.div>
);
}
whileHover — state when pointer is over the element.
whileTap — state when pointer is pressed.
whileFocus — state when element receives focus (useful for accessibility-visible focus animation).
whileDrag — state during drag (requires drag prop).
3. Exit Animations with AnimatePresence
Wrap conditionally rendered or list children in AnimatePresence to animate them out before unmount.
function NotificationList({ notifications }: { notifications: Notification[] }) {
return (
<AnimatePresence>
{notifications.map((n) => (
<motion.div
key={n.id}
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -100, height: 0 }}
transition={{ type: "spring", damping: 25 }}
>
{n.message}
</motion.div>
))}
</AnimatePresence>
);
}
HARD RULES for AnimatePresence:
- Every direct child must have a unique
key prop.
AnimatePresence only animates its direct children — do not wrap an extra layer between AnimatePresence and the motion components.
- The
exit prop is required on each child for exit animation to fire.
mode="wait" makes exit complete before enter starts; default mode fires both simultaneously.
4. Layout Animations
Add the layout prop to any motion element to automatically animate size and position changes.
function ExpandableCard({ isExpanded, onClick, children }: Props) {
return (
<motion.div
layout
onClick={onClick}
style={{
width: isExpanded ? 400 : 200,
height: isExpanded ? 300 : 100,
}}
transition={{ layout: { type: "spring", stiffness: 200 } }}
>
<motion.h3 layout="position">Title</motion.h3>
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{children}
</motion.div>
)}
</AnimatePresence>
</>
);
}
layout — animates any layout change (size + position).
layout="position" — animates only position, not size; prevents text reflow during animation.
layoutId="sharedId" — same layoutId on two elements in different components creates a shared animation between them (tab indicators, expanding image galleries).
Shared layout example:
function TabLayout({ activeTab }: { activeTab: string }) {
return (
<div className="tabs">
{tabs.map((tab) => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="activeTab"
className="underline"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
)}
</button>
))}
</div>
);
}
5. Scroll Animations
Scroll-triggered entrance with whileInView:
function ScrollReveal({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6 }}
>
{children}
</motion.div>
);
}
viewport={{ once: true }} — animate only the first time it enters viewport.
margin — offset the viewport detection boundary (e.g., "-100px" triggers 100px before fully visible).
Scroll-linked parallax with useScroll + useTransform:
import { motion, useScroll, useTransform } from "motion/react";
function ParallaxHero() {
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 500], [0, -150]);
const opacity = useTransform(scrollY, [0, 300], [1, 0]);
return (
<motion.div style={{ y, opacity }} className="hero">
<h1>Welcome</h1>
</motion.div>
);
}
useScroll() returns a MotionValue tracking scroll position.
useTransform(input, inputRange, outputRange) maps the scroll value to an output range.
- Pass
MotionValues to the style prop — do not pass them to animate or initial.
6. Staggered Children
Use variants with staggerChildren to sequence child animations.
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.1 },
},
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
function StaggeredList({ items }: { items: { id: string; name: string }[] }) {
return (
<motion.ul variants={container} initial="hidden" animate="show">
{items.map((i) => (
<motion.li key={i.id} variants={item}>
{i.name}
</motion.li>
))}
</motion.ul>
);
}
- Parent defines
staggerChildren in its transition.
- Children inherit the parent's
initial/animate variant labels automatically — do not set initial/animate on children if you want them to inherit.
- Each child must have
variants matching the same label names (hidden, show).
7. Motion Accessibility (Reduced Motion)
HARD RULE: Always respect prefers-reduced-motion. Motion does not automatically disable animations.
import { motion, useReducedMotion } from "motion/react";
function AccessibleFadeIn({ children }: { children: React.ReactNode }) {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: shouldReduceMotion ? 0.01 : 0.5 }}
>
{children}
</motion.div>
);
}
useReducedMotion() returns true when the user has reduced motion enabled at the OS/browser level.
- Design reduced-motion variants: disable movement (
y, x, scale), keep opacity changes instant or very short.
- Never block task completion behind an animation.
8. Motion System Design
When building a motion system for a product:
- Start from purpose: feedback, continuity, hierarchy, causality, spatial orientation, progress, or delight.
- Define motion tokens: duration, easing, delay, distance, spring behavior, reduced-motion behavior, and interruption rules.
- Map states: idle, hover, focus, active, pressed, loading, success, error, disabled, drag, drop, enter, exit, and route change.
- Choose technology by job:
- CSS transitions/animations — simple state changes, hover/focus
- Motion — React layout/component animation, shared layout, scroll-linked
- GSAP — complex timeline orchestration, ScrollTrigger
- Lottie/Rive — authored vector animations from design tools
- Three.js/WebGL — spatial 3D scenes
- Validate accessibility and performance before polish.
Examples
Spring vs Tween
transition={{ type: "spring", stiffness: 300, damping: 20 }}
transition={{ duration: 0.5, ease: "easeOut" }}
Keyframe Animations
<motion.div
animate={{
scale: [1, 1.2, 1],
rotate: [0, 180, 360],
}}
transition={{ duration: 2, repeat: Infinity, repeatType: "loop" }}
/>
Drag with Constraints
<motion.div
drag
dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}
dragElastic={0.2}
whileDrag={{ scale: 1.1 }}
onDragEnd={(e, info) => console.log(info.point.x, info.point.y)}
/>
Pitfalls
-
Missing key on AnimatePresence children — exit animations silently fail. Every direct child must have a stable, unique key.
-
Importing from "framer-motion" in new projects — use "motion/react" instead. The package was renamed. Legacy imports work but are deprecated for new code.
-
Passing MotionValue to animate or initial — MotionValues (from useScroll, useTransform) go in the style prop, not animate/initial. Mixing them causes errors or no animation.
-
Animating layout-triggering properties — avoid animating width, height, margin, padding, top, left directly when possible. Prefer transform (x, y, scale) and opacity which are GPU-accelerated and do not cause layout thrashing. Use the layout prop for size changes instead of manual width/height animation.
-
Forgetting prefers-reduced-motion — Motion does not auto-disable animations. You must check useReducedMotion() and provide reduced-motion variants. This is a WCAG 2.2 requirement (2.3.3 Animation from Interactions).
-
layout prop causing unexpected animations — adding layout to a container will animate any layout change, including those caused by content loading or font swap. Use layout="position" if you only want position changes animated.
-
staggerChildren not working — children must have variants with matching label names and must not set their own initial/animate props (they inherit from parent).
-
whileInView not triggering — check margin settings. If the element is below the fold and is set, it will only animate when scrolled into view. Ensure the element is not or zero-height.
Verification
Installation Check
npm ls motion
Expected: motion@x.x.x listed with no errors.
Import Path Check
Verify your imports use the correct path:
Select-String -Path "src\**\*.tsx" -Pattern "from [`"']framer-motion[`"']" -Recurse
If any results appear, migrate them to "motion/react".
Reduced Motion Check
Verify useReducedMotion is used in components with movement animations:
Select-String -Path "src\**\*.tsx" -Pattern "useReducedMotion" -Recurse
Any component using y, x, scale, rotate in initial/animate/whileHover should appear in results or have a documented reduced-motion fallback.
AnimatePresence Key Check
Verify every AnimatePresence child has a key:
Select-String -Path "src\**\*.tsx" -Pattern "AnimatePresence" -Recurse
Manually inspect each match to confirm direct children have unique key props.
Runtime Verification
- Mount animation: Load the page — element should animate from
initial to animate state on first render.
- Exit animation: Remove an item from the list — it should animate out (not instantly disappear).
- Layout animation: Toggle a size/position change on a
layout element — it should smoothly transition.
- Scroll animation: Scroll the page —
whileInView elements should animate when entering viewport; useScroll elements should move with scroll.
- Reduced motion: Enable reduced motion in OS settings (Windows: Settings → Accessibility → Visual effects → Animation effects → Off) — animations should be instant or minimal.
- Performance: Open Chrome DevTools → Performance tab → Record interaction → confirm no long tasks (>50ms) or layout thrashing during animation.
Related Skills
- css-animations — for simple state transitions that do not need React lifecycle integration
- gsap — for complex timeline orchestration and ScrollTrigger
- accessibility — for WCAG 2.2 compliance, focus management, and reduced-motion requirements
- design-system — for motion token definitions and design system integration
Current References