app-dev/framer-motion/SKILL.md

name: framer-motion description: >- Framer Motion / Motion for React patterns. Use when adding or debugging animation in React components that import from 'framer-motion' or 'motion/react' — motion.* elements, initial/animate/exit, AnimatePresence, layout/spring transitions — and especially when an animation re-fires, flickers, or fails to play because of React key / identity changes.

framer-motion

The one concept that causes most Framer Motion bugs: animations are driven by React identity (the key), not by prop values.

Version & import (check before editing)

  • 'framer-motion' (v10 through v13, still published) and 'motion/react' (the motion package from 11.11.12 on, which wraps the same library) expose the same motion / AnimatePresence API: import { motion, AnimatePresence } from 'framer-motion' or from 'motion/react'.
  • Match the repo's existing import path and pin; never mix the two paths unless deliberately doing a version bump. (Verified against npm 2026-09-03.)

The mental model: keys drive animation

Framer Motion animates a motion.* component when React mounts it (initial → animate) and when React unmounts it inside an <AnimatePresence> (animate → exit). React decides mount/unmount by the element's key (identity), not by whether its props changed.

Canonical write-up, for reference: https://www.nan.fyi/keys-in-framer-motion

Consequences you will hit:

  • Same key, changed props → no initial/exit; the existing element tweens toward the new animate target. This is what you want for "value updated, animate to it."
  • Changed key → React unmounts the old element and mounts a fresh one → exit then initial fire. This is how you intentionally replay an entrance animation (e.g. remount on a counter).
  • Accidentally-unstable key → the entrance animation re-fires on every render that produces a "new" element → flicker. In a list this looks like items popping/fading repeatedly.

Canvas libraries (React Flow and kin)

React Flow keys nodes/edges by id, so the id is the animation identity: stable ids keep inner motion.* elements mounted (no replayed initial); a changed id remounts the node and re-fires its entrance. If entrances flicker on a canvas, find what is changing identity — ids, or node/edge objects rebuilt with new references every render — and stabilize it; initial={false} only hides the symptom.

Idioms

  • Entrance = initial opacity/scale → animate to {opacity:1, scale:1}, spring on scale.
  • "Don't animate on first paint" = initial={false} on the motion element or on <AnimatePresence>.
  • Enter/exit of list items = wrap in <AnimatePresence>; use mode="popLayout" when siblings should reflow as one leaves.

AnimatePresence gotchas

  • Direct children of <AnimatePresence> must each have a stable, unique key and must be the conditionally-rendered element itself. A wrapper that's always mounted defeats exit.
  • exit only runs for elements removed while still inside an <AnimatePresence> that stays mounted. Unmounting the AnimatePresence itself skips exit animations.
  • mode="popLayout" pops the exiting element out of layout flow so the rest reflow immediately — good for vertical stacks, can look wrong for grids.

Quick checklist when an animation misbehaves

  1. Re-fires/flickers? The key changed when it shouldn't. Find the key (for canvas nodes, the id) or the identity-equality gap. Stabilize identity, don't add initial={false} to mask it.
  2. Won't replay when you want it to? The key is too stable — change it intentionally to remount.
  3. exit never plays? Element isn't a keyed direct child of a persistent <AnimatePresence>.
  4. Animating the wrong thing? Check which system owns the property first — a canvas library may position or tween nodes itself, leaving Framer Motion only enter/exit/scale/opacity — before adding motion props for it.

Related skills

  • react-flow-v12 — the @xyflow/react API (keys = node/edge id).