Motion
Motion is a production-grade animation library for React and JavaScript — formerly known as Framer Motion, now independent and expanded to support vanilla JS and other frameworks.
Documentation
- Docs: https://motion.dev/docs/
Key Capabilities
- Declarative animations:
motioncomponents animate viainitial,animate, andexitprops — no manual DOM manipulation - Variants: Named animation states shared across component trees, with automatic stagger and orchestration
- Exit animations:
AnimatePresencekeeps unmounting components in the DOM until theirexitanimation completes - Layout animations:
layoutprop uses FLIP technique to animate any CSS layout change automatically - Gestures: Built-in
whileHover,whileTap,whileDrag, andwhileFocusprops with full event callbacks - Motion values:
useMotionValueanduseTransformcreate composable, signal-like values that update outside React's render cycle - Imperative API:
animate()function anduseAnimatehook for programmatic control, usable outside React - Spring physics: Default transition is spring-based; configure with
type: "spring",stiffness,damping,mass - Scroll animations:
useScrollanduseTransformfor scroll-linked animations without scroll event listeners
Best Practices
-
Package is now
motion, import frommotion/reactfor React — the oldframer-motionpackage still works as a compatibility shim, but new projects should installmotionand import frommotion/react. Never mix imports from both packages in the same project.// Correct (new) import { motion, AnimatePresence } from "motion/react" // Old (still works but deprecated) import { motion, AnimatePresence } from "framer-motion" -
AnimatePresencerequires a changingkeyprop on the child — missing key means exit animations never fire —AnimatePresencetracks children by theirkey. If the key stays the same, Motion sees no unmount and theexitanimation is skipped entirely. Always give the animated child a key tied to what changes.// Wrong — key never changes, exit animation skipped <AnimatePresence> {show && <motion.div exit={{ opacity: 0 }}>Hello</motion.div>} </AnimatePresence> // Correct — key changes when content changes <AnimatePresence> {show && <motion.div key="hello" exit={{ opacity: 0 }}>Hello</motion.div>} </AnimatePresence> -
Conditional rendering with
&&bypassesAnimatePresence— when you write{condition && component}, React removes the element from the tree immediately whenconditionis false.AnimatePresencenever gets a chance to play the exit animation. Always place the condition insideAnimatePresenceas a child, not outside it.// Wrong — component removed before exit animation can play {isVisible && ( <AnimatePresence> <motion.div key="box" exit={{ opacity: 0 }} /> </AnimatePresence> )} // Correct — AnimatePresence controls the unmount <AnimatePresence> {isVisible && <motion.div key="box" exit={{ opacity: 0 }} />} </AnimatePresence> -
Layout animations across separate components require
LayoutGroup— thelayoutprop animates an element's own position/size changes. When a layout change in one component should trigger layout animations in sibling or unrelated components, wrap them all inLayoutGroup. Without it, each component animates independently and the effect looks disconnected.import { LayoutGroup } from "motion/react" <LayoutGroup> <Sidebar /> {/* layout prop inside */} <MainContent /> {/* layout prop inside — will coordinate with Sidebar */} </LayoutGroup> -
useMotionValueanduseTransformvalues do not trigger re-renders — do not read them in render logic — motion values update synchronously and bypass React's reconciler for performance. Reading a motion value with.get()inside a render returns a snapshot that will not cause the component to re-render when it changes. UseuseMotionValueEventoruseTransformto react to changes, ormotioncomponent props to bind them to the DOM.const x = useMotionValue(0) // Wrong — x.get() won't trigger re-renders, displayed value will be stale return <div>Position: {x.get()}</div> // Correct — bind to a motion component prop; Motion updates the DOM directly return <motion.div style={{ x }} /> // Correct — subscribe to changes with useMotionValueEvent useMotionValueEvent(x, "change", (latest) => console.log(latest)) -
motioncomponents are React-only;animate()is the framework-agnostic imperative API — usemotion.div,motion.span, etc. only in React component trees. For animations in utility functions, vanilla JS, or outside JSX, use theanimate()function imported frommotion(notmotion/react).useAnimateis a React hook that provides a scoped imperative API for complex sequences inside components.// React component — use motion components import { motion } from "motion/react" const Card = () => <motion.div animate={{ opacity: 1 }} /> // Vanilla JS / utility — use animate() from "motion" import { animate } from "motion" animate("#box", { opacity: 1 }, { duration: 0.3 }) // Complex sequences inside React — use useAnimate import { useAnimate } from "motion/react" const [scope, animate] = useAnimate()