motion-react — Motion for React
Overview
Implements UI animation in React with Motion — the library previously published as
framer-motion, independent since late 2024 and published as the npm package motion
(import from "motion/react"). The skill covers the DOM product-UI animation surface:
declarative enter/exit, variant orchestration, layout and shared-element transitions,
motion values and number tickers, and gesture states — with three non-negotiables woven
through: reduced-motion accessibility, compositor-friendly performance, and
restraint (purposeful, short, consistent motion — especially in data-dense tools).
It also owns the "do I even need Motion?" decision: many jobs are better served by plain
CSS or a one-line AutoAnimate call.
When to activate
- ✅ Adding enter/exit/list animations, page or view transitions, shared-element moves, animated counters, or gesture feedback to a React SPA.
- ✅ Migrating or writing code that references
framer-motion (the old package) — this skill teaches the current motion idioms.
- ✅ Wiring reduced-motion support, or auditing existing animation for accessibility/performance.
- ✅ Choosing between Motion, CSS/Tailwind transitions, and AutoAnimate for a given interaction.
Do NOT activate when:
- The task is animated illustrations / Lottie files — that is imagery, not UI motion (a dedicated illustrations skill owns it).
- The task is video rendering (Remotion) or scroll-storytelling/parallax marketing pages — different genre; only
whileInView basics are covered here.
- The product's motion design language (which durations/easings a specific app adopts) is being decided — that is a design-system decision; this skill supplies the vocabulary and constraints.
Workflow
Step 1: Choose the right tool — most animations don't need Motion
| Job |
Reach for |
Why |
| Hover/focus color, opacity, small scale |
Plain CSS / Tailwind transition-* |
Zero JS, zero bundle, GPU-friendly |
| Open/close styling on Radix-based components (accordions, popovers, dialogs) |
CSS keyed off Radix data-state attributes |
The primitive already exposes state; CSS animates it |
| List add / remove / reorder in an internal tool |
AutoAnimate — one useAutoAnimate() ref on the parent |
Zero-config, animates child add/remove/move, respects prefers-reduced-motion automatically |
Exit animations on unmount, orchestrated sequences, shared-element moves, springs, number tickers, drag / drag-to-reorder (Reorder components) |
Motion |
Needs JS lifecycle control CSS cannot express |
| Browser-native view morphs (same-document, or cross-document/MPA) |
View Transitions API |
Named for awareness; for view/route swaps in a React SPA, AnimatePresence covers the equivalent (whole-document morphs like a theme crossfade remain VTA-only) |
Escalate down the table only when the simpler tier genuinely cannot do the job.
Step 2: Install and set the app-level posture
npm install motion
import { motion, MotionConfig } from "motion/react"
// App root — respect the user's OS reduced-motion setting everywhere:
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>
- Package is
motion; React entry is "motion/react". React 18+ (docs state 18.2; the
peer range is ^18.0.0 || ^19.0.0). Anything importing
framer-motion is the pre-rename package — same lineage, but new projects use motion
and current docs/idioms live under that name.
MotionConfig reducedMotion accepts "user" (respect the OS setting), "always"
(force-reduce; debugging), "never" (default). Under reduction, Motion disables
transform and layout animations while keeping opacity/color animations — usually exactly
the right degradation. Set "user" at the root as the default posture; see
references/a11y-and-restraint.md for the full contract.
- The reduced-bundle wiring is one wrapper:
<LazyMotion features={domAnimation} strict><m.div animate={{ opacity: 1 }} /></LazyMotion>.
- React Server Component frameworks:
motion components are client components — add a
"use client" boundary (or import from motion/react-client); a plain Vite SPA needs
neither.
- Bundle-sensitive apps:
LazyMotion + the m component (import * as m from "motion/react-m") renders for ~4.6 kB initially (vs ~34 kB for full motion), with
features (domAnimation) loaded sync or — for the full saving — async after
hydration; strict on LazyMotion throws if a full motion component sneaks in.
Step 3: Core animation
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>
initial → animate runs on mount; exit runs on unmount only inside
AnimatePresence (Step 5).
transition picks the model: type: "tween" (duration + easing — UI default) or
type: "spring" (physical; use for gestures/drag, sparingly for entrances). Keyframes:
pass arrays (animate={{ x: [0, 100, 0] }}); null as the first keyframe means
"current value".
- Gesture states:
whileHover, whileTap, whileFocus, whileDrag, whileInView —
values revert when the gesture ends. Prefer CSS for hover-only styling (Step 1); use
these when the gesture must coordinate with other animated values.
- Product-UI durations: 150–300 ms for micro-interactions, ≤ 400 ms for larger
transitions. Longer reads as lag, not polish.
Step 4: Variants — orchestrate parent and children
import { motion, stagger } from "motion/react"
const list = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { when: "beforeChildren", delayChildren: stagger(0.05) },
},
}
const item = { hidden: { opacity: 0, y: 4 }, visible: { opacity: 1, y: 0 } }
<motion.ul variants={list} initial="hidden" animate="visible">
{rows.map(r => <motion.li key={r.id} variants={item} />)}
</motion.ul>
- Variant names flow down: children with
variants animate when the parent's state
changes — no prop drilling.
- Orchestration lives in the parent's
transition: when: "beforeChildren" | "afterChildren", and delayChildren: stagger(interval) — the current idiom,
available since motion 12.22.0 (staggerChildren/staggerDirection were
deprecated in 12.21.0). On older installs (framer-motion, early 12.x) staggerChildren: 0.05 still works and stagger()-in-delayChildren is not supported (the option was
a plain number there) — bump the package before migrating the idiom.
- Dynamic variants: a variant can be a function of
custom (e.g. per-index delay).
Step 5: AnimatePresence — exit animations
import { AnimatePresence, motion } from "motion/react"
<AnimatePresence mode="popLayout">
{rows.map(r => (
<motion.div key={r.id} exit={{ opacity: 0, x: -8 }} layout />
))}
</AnimatePresence>
- Works by watching its direct children; every child needs a stable, unique
key
(array indices break exit animations — the classic failure).
mode: "sync" (default — in/out simultaneously), "wait" (out fully before in; one
child at a time — right for view/route swaps), "popLayout" (exiting elements pop out
of layout flow so siblings reflow — right for lists; the parent needs non-static
position).
- In-place content swaps (skeleton→content, tab panels): with
"wait", match the two
children's dimensions or the gap flashes; when dimensions differ, crossfade in place
via "popLayout" or a grid-stack/absolute overlay — and pair the container with
layout if the size change itself should animate rather than snap.
initial={false} skips mount animation for children present at first render.
onExitComplete fires when all exits finish.
- Exiting components cannot receive prop updates — for direction-aware exits (carousels,
paginated views), pass the direction via AnimatePresence's
custom prop and read it
with usePresenceData (or variant custom) inside the child.
- AnimatePresence must wrap the conditional — never be inside it.
Step 6: Beyond the core — load the reference that matches the job
- Layout animations,
layoutId shared elements, LayoutGroup, distortion fixes →
references/layout-and-shared-elements.md
- Motion values,
useTransform/useSpring, imperative animate()/useAnimate,
animated number tickers for stat tiles → references/values-and-imperative.md
- The full reduced-motion contract, WCAG mapping, reduce-vs-keep table, motion-restraint
tokens →
references/a11y-and-restraint.md
- Radix/shadcn integration (
forceMount), Tailwind boundaries, router page transitions,
the AutoAnimate worked example, and testing (MotionGlobalConfig.skipAnimations,
Playwright reduced-motion) → references/integration-and-testing.md
Rules
Hard rules (never violate):
- Respect reduced motion. Every Motion-using app sets
MotionConfig reducedMotion="user"
(or an equivalent explicit strategy) at the root. Never ship large translations, zooms,
parallax, or auto-playing motion that ignores the OS preference (WCAG 2.3.3; autoplaying
5 s motion needs a pause control per WCAG 2.2.2).
- Animate
transform and opacity, not layout-inducing properties. Never animate
width/height/top/left/margins directly — use the layout prop (which animates
via transforms) or scale; cheap non-layout properties (color/background, SVG stroke
values) are fine. Scale-based fills (progress bars, meters) need an explicit transform
origin (originX: 0 for a left-anchored fill) — the default is center. One sanctioned exception: expand/collapse reveals
(accordion, disclosure) animate height: 0 ↔ "auto" with overflow: hidden — scale
distorts text and layout can't clip-reveal; keep it short and user-initiated. Note
reduced motion does NOT auto-reduce it (only transform/layout animations are
disabled) — if the reveal displaces substantial content, gate it with
useReducedMotion() to snap open instead.
- Stable keys inside AnimatePresence. Array indices as keys silently break exit
animations.
- Import from
"motion/react". Do not add framer-motion to a new project; do not
mix both packages.
- Motion must be purposeful. Animation communicates state change, spatial
relationship, or feedback — decorative-only animation in a product UI is a defect, not
polish.
Preferences (override-able):
- Micro-interactions 150–300 ms; route/page swaps ≤ 200 ms; other view-level
transitions ≤ 400 ms (the outer bound, not the target).
- Tweens for UI state changes; springs for gesture-driven/physical interactions.
- Centralize durations/easings as design tokens rather than scattering literals.
- Prefer the simplest sufficient tier (Step 1) before reaching for Motion.
Gotchas
- Exit animation never plays: the element isn't a direct child of
AnimatePresence,
its key is unstable, or the AnimatePresence itself unmounts with the element. Fix the
tree, not the animation.
- Old-package drift: tutorials and LLM completions frequently emit
framer-motion
imports, staggerChildren, or motion(Component)-era idioms. Current: motion pkg,
delayChildren: stagger(), and motion.create(Component) to wrap a custom component
(it must expose its ref — forwardRef on React 18, the plain ref prop on React 19);
check the live docs when an API looks dated.
popLayout needs positioning: exiting elements are absolutely positioned; a
static-positioned parent misplaces them.
- Layout animation distorts rounded corners/shadows: set
borderRadius/boxShadow
via style (or animate them) so Motion can scale-correct; give stretching children
their own layout prop.
- Springs on mount feel bouncy-slow: physics-configured springs
(stiffness/damping) derive their own settle time and ignore duration expectations;
use a duration-based spring (
type: "spring", duration, bounce) or a 200 ms tween
for entrances.
- Motion values don't re-render React: rendering
{x.get()} in JSX goes stale —
render the motion value as a motion element child or subscribe via
useMotionValueEvent.
- Tests hang or flake on animations: set
MotionGlobalConfig.skipAnimations = true
in the test setup and assert end-states (see the integration reference).
- jsdom has no layout: layout animations and
whileInView need real measurements;
unit-test the state logic, e2e-test the visuals.
Anti-patterns
- "Animate everything — it looks premium." Data-dense tools earn polish through a few
consistent, short, purposeful animations; motion noise slows comprehension.
- "Skip reduced-motion for now, add it later." It's a root-level one-liner when done
first and an audit when done later. There is no "later".
- "Just animate the height." Layout properties jank on the main thread;
layout prop
or a different design.
- "AutoAnimate is beneath us — rewrite the list in Motion." If add/remove/move is all
the list needs, one ref beats thirty lines of variants.
- "Copy the framer-motion snippet from that 2023 blog post." Verify against current
docs; the package, imports, and stagger idiom changed.
Output
Working animation code in a React codebase: components using motion/react primitives
(or deliberately simpler tiers per the Step-1 table), an app-level reduced-motion posture,
and test setup that keeps animated components deterministic. Consumers are the codebase's
reviewers and test suite — animation that ships with accessibility, performance, and
restraint already satisfied.
Related
- A UI/UX design-guidelines skill (e.g. one carrying animation duration/meaning rules) is
the METHOD source for when/what to animate; this skill is the implementation
counterpart.
- A component-styling skill (Radix/Tailwind) owns the
data-state CSS tier of the Step-1
table.
- A router skill (e.g. TanStack Router) owns route structure; this skill only wraps route
views for transitions.
- An illustrations skill owns Lottie/animated imagery — adjacent, not this skill's
surface.
- A component-testing skill owns the RTL/vitest harness this skill's testing guidance
plugs into.
Progressive disclosure
references/layout-and-shared-elements.md — load when animating layout changes, shared
elements (layoutId), or reordering lists.
references/values-and-imperative.md — load for motion values, transforms/springs,
imperative sequences, or animated number tickers.
references/a11y-and-restraint.md — load when wiring reduced-motion, auditing
accessibility, or defining motion tokens/restraint rules.
references/integration-and-testing.md — load when combining Motion with Radix/shadcn,
Tailwind, or a router, when using AutoAnimate, or when tests involve animated
components.
references/sources.md — research provenance; load only when auditing claims.
Body budget
description ≤ 1,024 chars (agentskills.io cap).
- Body ≤ ~500 lines / 5,000 tokens soft target; overflow lives in
references/.
1---2name: motion-react3description: Use when adding animation to a React app with the Motion library (the framer-motion successor: npm package `motion`, import `motion/react`) — enter/exit animations, variants and stagger, AnimatePresence, layout and shared-element transitions, animated number tickers, gesture states — or when deciding whether a job needs Motion at all versus plain CSS/Tailwind transitions or AutoAnimate. Covers reduced-motion accessibility (MotionConfig, useReducedMotion, WCAG 2.3.3/2.2.2) as a first-class requirement, performance discipline (transform/opacity only), motion restraint for data-dense product UI, Radix/shadcn and router integration, and keeping tests stable (MotionGlobalConfig.skipAnimations). Keywords: motion, framer-motion, animation, AnimatePresence, layout animation, micro-interactions, reduced motion, page transitions, stagger, spring.4---56# motion-react — Motion for React78## Overview910Implements UI animation in React with **Motion** — the library previously published as11`framer-motion`, independent since late 2024 and published as the npm package `motion`12(import from `"motion/react"`). The skill covers the DOM product-UI animation surface:13declarative enter/exit, variant orchestration, layout and shared-element transitions,14motion values and number tickers, and gesture states — with three non-negotiables woven15through: **reduced-motion accessibility**, **compositor-friendly performance**, and16**restraint** (purposeful, short, consistent motion — especially in data-dense tools).17It also owns the "do I even need Motion?" decision: many jobs are better served by plain18CSS or a one-line AutoAnimate call.1920## When to activate2122- ✅ Adding enter/exit/list animations, page or view transitions, shared-element moves, animated counters, or gesture feedback to a React SPA.23- ✅ Migrating or writing code that references `framer-motion` (the old package) — this skill teaches the current `motion` idioms.24- ✅ Wiring reduced-motion support, or auditing existing animation for accessibility/performance.25- ✅ Choosing between Motion, CSS/Tailwind transitions, and AutoAnimate for a given interaction.2627**Do NOT activate when:**2829- The task is animated illustrations / Lottie files — that is imagery, not UI motion (a dedicated illustrations skill owns it).30- The task is video rendering (Remotion) or scroll-storytelling/parallax marketing pages — different genre; only `whileInView` basics are covered here.31- The product's motion design language (which durations/easings a specific app adopts) is being decided — that is a design-system decision; this skill supplies the vocabulary and constraints.3233## Workflow3435### Step 1: Choose the right tool — most animations don't need Motion3637| Job | Reach for | Why |38|---|---|---|39| Hover/focus color, opacity, small scale | Plain CSS / Tailwind `transition-*` | Zero JS, zero bundle, GPU-friendly |40| Open/close styling on Radix-based components (accordions, popovers, dialogs) | CSS keyed off Radix `data-state` attributes | The primitive already exposes state; CSS animates it |41| List add / remove / reorder in an internal tool | AutoAnimate — one `useAutoAnimate()` ref on the parent | Zero-config, animates child add/remove/move, respects `prefers-reduced-motion` automatically |42| Exit animations on unmount, orchestrated sequences, shared-element moves, springs, number tickers, drag / drag-to-reorder (`Reorder` components) | **Motion** | Needs JS lifecycle control CSS cannot express |43| Browser-native view morphs (same-document, or cross-document/MPA) | View Transitions API | Named for awareness; for view/route swaps in a React SPA, AnimatePresence covers the equivalent (whole-document morphs like a theme crossfade remain VTA-only) |4445Escalate down the table only when the simpler tier genuinely cannot do the job.4647### Step 2: Install and set the app-level posture4849```bash50npm install motion51```5253```tsx54import { motion, MotionConfig } from "motion/react"5556// App root — respect the user's OS reduced-motion setting everywhere:57<MotionConfig reducedMotion="user">58 <App />59</MotionConfig>60```6162- Package is `motion`; React entry is `"motion/react"`. React 18+ (docs state 18.2; the63 peer range is `^18.0.0 || ^19.0.0`). Anything importing64 `framer-motion` is the pre-rename package — same lineage, but new projects use `motion`65 and current docs/idioms live under that name.66- `MotionConfig reducedMotion` accepts `"user"` (respect the OS setting), `"always"`67 (force-reduce; debugging), `"never"` (default). Under reduction, Motion disables68 transform and layout animations while keeping opacity/color animations — usually exactly69 the right degradation. Set `"user"` at the root as the default posture; see70 `references/a11y-and-restraint.md` for the full contract.71- The reduced-bundle wiring is one wrapper:72 `<LazyMotion features={domAnimation} strict><m.div animate={{ opacity: 1 }} /></LazyMotion>`.73- React Server Component frameworks: `motion` components are client components — add a74 `"use client"` boundary (or import from `motion/react-client`); a plain Vite SPA needs75 neither.76- Bundle-sensitive apps: `LazyMotion` + the `m` component (`import * as m from77 "motion/react-m"`) renders for ~4.6 kB initially (vs ~34 kB for full `motion`), with78 features (`domAnimation`) loaded sync or — for the full saving — async after79 hydration; `strict` on `LazyMotion` throws if a full `motion` component sneaks in.8081### Step 3: Core animation8283```tsx84<motion.div85 initial={{ opacity: 0, y: 8 }}86 animate={{ opacity: 1, y: 0 }}87 exit={{ opacity: 0 }}88 transition={{ duration: 0.2, ease: "easeOut" }}89/>90```9192- `initial` → `animate` runs on mount; `exit` runs on unmount **only inside93 `AnimatePresence`** (Step 5).94- `transition` picks the model: `type: "tween"` (duration + easing — UI default) or95 `type: "spring"` (physical; use for gestures/drag, sparingly for entrances). Keyframes:96 pass arrays (`animate={{ x: [0, 100, 0] }}`); `null` as the first keyframe means97 "current value".98- Gesture states: `whileHover`, `whileTap`, `whileFocus`, `whileDrag`, `whileInView` —99 values revert when the gesture ends. Prefer CSS for hover-only styling (Step 1); use100 these when the gesture must coordinate with other animated values.101- Product-UI durations: 150–300 ms for micro-interactions, ≤ 400 ms for larger102 transitions. Longer reads as lag, not polish.103104### Step 4: Variants — orchestrate parent and children105106```tsx107import { motion, stagger } from "motion/react"108109const list = {110 hidden: { opacity: 0 },111 visible: {112 opacity: 1,113 transition: { when: "beforeChildren", delayChildren: stagger(0.05) },114 },115}116const item = { hidden: { opacity: 0, y: 4 }, visible: { opacity: 1, y: 0 } }117118<motion.ul variants={list} initial="hidden" animate="visible">119 {rows.map(r => <motion.li key={r.id} variants={item} />)}120</motion.ul>121```122123- Variant names flow down: children with `variants` animate when the parent's state124 changes — no prop drilling.125- Orchestration lives in the parent's `transition`: `when: "beforeChildren" |126 "afterChildren"`, and **`delayChildren: stagger(interval)`** — the current idiom,127 available since `motion` 12.22.0 (`staggerChildren`/`staggerDirection` were128 deprecated in 12.21.0). On older installs (`framer-motion`, early 12.x) `staggerChildren:129 0.05` still works and `stagger()`-in-`delayChildren` is not supported (the option was130 a plain number there) — bump the package before migrating the idiom.131- Dynamic variants: a variant can be a function of `custom` (e.g. per-index delay).132133### Step 5: AnimatePresence — exit animations134135```tsx136import { AnimatePresence, motion } from "motion/react"137138<AnimatePresence mode="popLayout">139 {rows.map(r => (140 <motion.div key={r.id} exit={{ opacity: 0, x: -8 }} layout />141 ))}142</AnimatePresence>143```144145- Works by watching its **direct children**; every child needs a **stable, unique `key`**146 (array indices break exit animations — the classic failure).147- `mode`: `"sync"` (default — in/out simultaneously), `"wait"` (out fully before in; one148 child at a time — right for view/route swaps), `"popLayout"` (exiting elements pop out149 of layout flow so siblings reflow — right for lists; the parent needs non-`static`150 `position`).151- In-place content swaps (skeleton→content, tab panels): with `"wait"`, match the two152 children's dimensions or the gap flashes; when dimensions differ, crossfade in place153 via `"popLayout"` or a grid-stack/absolute overlay — and pair the container with154 `layout` if the size change itself should animate rather than snap.155- `initial={false}` skips mount animation for children present at first render.156 `onExitComplete` fires when all exits finish.157- Exiting components cannot receive prop updates — for direction-aware exits (carousels,158 paginated views), pass the direction via AnimatePresence's `custom` prop and read it159 with `usePresenceData` (or variant `custom`) inside the child.160- AnimatePresence must wrap the conditional — never be inside it.161162### Step 6: Beyond the core — load the reference that matches the job163164- Layout animations, `layoutId` shared elements, `LayoutGroup`, distortion fixes →165 `references/layout-and-shared-elements.md`166- Motion values, `useTransform`/`useSpring`, imperative `animate()`/`useAnimate`,167 animated number tickers for stat tiles → `references/values-and-imperative.md`168- The full reduced-motion contract, WCAG mapping, reduce-vs-keep table, motion-restraint169 tokens → `references/a11y-and-restraint.md`170- Radix/shadcn integration (`forceMount`), Tailwind boundaries, router page transitions,171 the AutoAnimate worked example, and testing (`MotionGlobalConfig.skipAnimations`,172 Playwright reduced-motion) → `references/integration-and-testing.md`173174## Rules175176**Hard rules (never violate):**177178- **Respect reduced motion.** Every Motion-using app sets `MotionConfig reducedMotion="user"`179 (or an equivalent explicit strategy) at the root. Never ship large translations, zooms,180 parallax, or auto-playing motion that ignores the OS preference (WCAG 2.3.3; autoplaying181 >5 s motion needs a pause control per WCAG 2.2.2).182- **Animate `transform` and `opacity`, not layout-inducing properties.** Never animate183 `width`/`height`/`top`/`left`/margins directly — use the `layout` prop (which animates184 via transforms) or scale; cheap non-layout properties (color/background, SVG stroke185 values) are fine. Scale-based fills (progress bars, meters) need an explicit transform186 origin (`originX: 0` for a left-anchored fill) — the default is center. **One sanctioned exception:** expand/collapse reveals187 (accordion, disclosure) animate `height: 0 ↔ "auto"` with `overflow: hidden` — scale188 distorts text and `layout` can't clip-reveal; keep it short and user-initiated. Note189 reduced motion does NOT auto-reduce it (only transform/layout animations are190 disabled) — if the reveal displaces substantial content, gate it with191 `useReducedMotion()` to snap open instead.192- **Stable keys inside AnimatePresence.** Array indices as keys silently break exit193 animations.194- **Import from `"motion/react"`.** Do not add `framer-motion` to a new project; do not195 mix both packages.196- **Motion must be purposeful.** Animation communicates state change, spatial197 relationship, or feedback — decorative-only animation in a product UI is a defect, not198 polish.199200**Preferences (override-able):**201202- Micro-interactions 150–300 ms; route/page swaps ≤ 200 ms; other view-level203 transitions ≤ 400 ms (the outer bound, not the target).204- Tweens for UI state changes; springs for gesture-driven/physical interactions.205- Centralize durations/easings as design tokens rather than scattering literals.206- Prefer the simplest sufficient tier (Step 1) before reaching for Motion.207208## Gotchas209210- **Exit animation never plays:** the element isn't a direct child of `AnimatePresence`,211 its key is unstable, or the `AnimatePresence` itself unmounts with the element. Fix the212 tree, not the animation.213- **Old-package drift:** tutorials and LLM completions frequently emit `framer-motion`214 imports, `staggerChildren`, or `motion(Component)`-era idioms. Current: `motion` pkg,215 `delayChildren: stagger()`, and `motion.create(Component)` to wrap a custom component216 (it must expose its ref — `forwardRef` on React 18, the plain `ref` prop on React 19);217 check the live docs when an API looks dated.218- **`popLayout` needs positioning:** exiting elements are absolutely positioned; a219 `static`-positioned parent misplaces them.220- **Layout animation distorts rounded corners/shadows:** set `borderRadius`/`boxShadow`221 via `style` (or animate them) so Motion can scale-correct; give stretching children222 their own `layout` prop.223- **Springs on mount feel bouncy-slow:** physics-configured springs224 (stiffness/damping) derive their own settle time and ignore duration expectations;225 use a duration-based spring (`type: "spring", duration, bounce`) or a 200 ms tween226 for entrances.227- **Motion values don't re-render React:** rendering `{x.get()}` in JSX goes stale —228 render the motion value as a `motion` element child or subscribe via229 `useMotionValueEvent`.230- **Tests hang or flake on animations:** set `MotionGlobalConfig.skipAnimations = true`231 in the test setup and assert end-states (see the integration reference).232- **jsdom has no layout:** layout animations and `whileInView` need real measurements;233 unit-test the state logic, e2e-test the visuals.234235## Anti-patterns236237- **"Animate everything — it looks premium."** Data-dense tools earn polish through a few238 consistent, short, purposeful animations; motion noise slows comprehension.239- **"Skip reduced-motion for now, add it later."** It's a root-level one-liner when done240 first and an audit when done later. There is no "later".241- **"Just animate the height."** Layout properties jank on the main thread; `layout` prop242 or a different design.243- **"AutoAnimate is beneath us — rewrite the list in Motion."** If add/remove/move is all244 the list needs, one ref beats thirty lines of variants.245- **"Copy the framer-motion snippet from that 2023 blog post."** Verify against current246 docs; the package, imports, and stagger idiom changed.247248## Output249250Working animation code in a React codebase: components using `motion/react` primitives251(or deliberately simpler tiers per the Step-1 table), an app-level reduced-motion posture,252and test setup that keeps animated components deterministic. Consumers are the codebase's253reviewers and test suite — animation that ships with accessibility, performance, and254restraint already satisfied.255256## Related257258- A UI/UX design-guidelines skill (e.g. one carrying animation duration/meaning rules) is259 the METHOD source for when/what to animate; this skill is the implementation260 counterpart.261- A component-styling skill (Radix/Tailwind) owns the `data-state` CSS tier of the Step-1262 table.263- A router skill (e.g. TanStack Router) owns route structure; this skill only wraps route264 views for transitions.265- An illustrations skill owns Lottie/animated imagery — adjacent, not this skill's266 surface.267- A component-testing skill owns the RTL/vitest harness this skill's testing guidance268 plugs into.269270## Progressive disclosure271272- `references/layout-and-shared-elements.md` — load when animating layout changes, shared273 elements (`layoutId`), or reordering lists.274- `references/values-and-imperative.md` — load for motion values, transforms/springs,275 imperative sequences, or animated number tickers.276- `references/a11y-and-restraint.md` — load when wiring reduced-motion, auditing277 accessibility, or defining motion tokens/restraint rules.278- `references/integration-and-testing.md` — load when combining Motion with Radix/shadcn,279 Tailwind, or a router, when using AutoAnimate, or when tests involve animated280 components.281- `references/sources.md` — research provenance; load only when auditing claims.282283## Body budget284285- `description` ≤ 1,024 chars (agentskills.io cap).286- Body ≤ ~500 lines / 5,000 tokens soft target; overflow lives in `references/`.