SuperAPI AI Directory

motion-react

Frontend & websites

View repository

Updated 10-15 20:07

About this skill

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/.