Patterns

Patterns — Motion

Variants for Coordinated State

When to use: multiple elements (or a parent + children) need to animate together based on shared state (open/closed, hovered, selected). How: define a variants object with named states on the parent; pass the same variant names to children without repeating the target values per child. Motion propagates variant changes down the tree automatically. Trade-offs: requires all children to be motion components; deeply nested custom-prop-driven variants can get harder to trace than inline animate props for a one-off case.

AnimatePresence for Exit Animations

When to use: a component needs to animate out before unmounting (list item removal, modal close, route change). How: wrap the conditionally-rendered tree in <AnimatePresence>, give the exiting element a stable key, and define an exit variant/prop. Trade-offs: only works for direct children of AnimatePresence; conditional rendering logic has to keep the exiting element mounted until Motion signals it's done.

Layout Animation Instead of Manual FLIP

When to use: an element's position or size changes due to a layout shift (reordering, resizing, conditional rendering) and you want it to animate rather than jump. How: add the layout prop (or layoutId for shared-element transitions across different components) instead of hand-computing FLIP transforms. Trade-offs: layout animation samples layout every frame during the transition, expensive on very large trees, scope it to the smallest subtree that needs it.

Orchestration with Stagger and Delay

When to use: a list or group of children should animate in sequence rather than simultaneously. How: set staggerChildren (and optionally delayChildren) on the parent variant's transition, children inherit the offset automatically if they use the same variant names. Trade-offs: stagger timing is relative to the parent's animation start, not absolute; large lists need a capped/virtualized stagger or the last items lag noticeably.

Motion Values for High-Frequency Updates

When to use: a value changes every frame (drag position, scroll offset, cursor tracking) and re-rendering React on every update would be too slow. How: read/write through useMotionValue/useSpring/useTransform instead of useState; bind the motion value directly to a style prop so updates bypass React's render cycle. Trade-offs: motion values don't trigger re-renders by default, if other UI needs to react to the value, subscribe explicitly with useMotionValueEvent.

Composed Gestures

When to use: an element needs to respond to more than one interaction (e.g. drag AND hover AND tap) with different visual states for each. How: combine whileHover, whileTap, whileDrag, whileFocus on the same motion component, each defines its own target state and Motion resolves the active one. Trade-offs: overlapping gesture states (e.g. hover while dragging) resolve by gesture priority, verify the resolved behavior matches intent rather than assuming pure layering.

Reduced-Motion-Safe Animation

When to use: any animation that should respect the OS-level "reduce motion" accessibility setting. How: set the reduced-motion policy once via MotionConfig's reducedMotion prop ("user" or "always") instead of checking the media query per-component. Trade-offs: "always" disables transforms/layout animation globally even for users who didn't request it, only use it when the product genuinely never wants motion.

Performance-First Property Choice

When to use: any animation running continuously or on a hot path (scroll, drag, per-frame updates). How: animate transform and opacity wherever possible (compositor-only, no layout/paint), avoid animating width/height/top/left/box-shadow-like properties in loops. Trade-offs: some effects (e.g. true layout reflow) can't be expressed as transform-only, use the layout prop's FLIP-based approach instead of animating layout properties directly.