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.
-
Package is now motion, import from motion/react for React — the old framer-motion package still works as a compatibility shim, but new projects should install motion and import from motion/react. Never mix imports from both packages in the same project.
import { motion, AnimatePresence } from "motion/react"
import { motion, AnimatePresence } from "framer-motion"
-
AnimatePresence requires a changing key prop on the child — missing key means exit animations never fire — AnimatePresence tracks children by their key. If the key stays the same, Motion sees no unmount and the exit animation is skipped entirely. Always give the animated child a key tied to what changes.
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }}>Hello</motion.div>}
</AnimatePresence>
<AnimatePresence>
{show && <motion.div key="hello" exit={{ opacity: 0 }}>Hello</motion.div>}
</AnimatePresence>
-
Conditional rendering with && bypasses AnimatePresence — when you write {condition && component}, React removes the element from the tree immediately when condition is false. AnimatePresence never gets a chance to play the exit animation. Always place the condition inside AnimatePresence as a child, not outside it.
{isVisible && (
<AnimatePresence>
<motion.div key="box" exit={{ opacity: 0 }} />
</AnimatePresence>
)}
<AnimatePresence>
{isVisible && <motion.div key="box" exit={{ opacity: 0 }} />}
</AnimatePresence>
-
Layout animations across separate components require LayoutGroup — the layout prop 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 in LayoutGroup. Without it, each component animates independently and the effect looks disconnected.
import { LayoutGroup } from "motion/react"
<LayoutGroup>
<Sidebar /> {}
<MainContent /> {}
</LayoutGroup>
-
useMotionValue and useTransform values 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. Use useMotionValueEvent or useTransform to react to changes, or motion component props to bind them to the DOM.
const x = useMotionValue(0)
return <div>Position: {x.get()}</div>
return <motion.div style={{ x }} />
useMotionValueEvent(x, "change", (latest) => console.log(latest))
-
motion components are React-only; animate() is the framework-agnostic imperative API — use motion.div, motion.span, etc. only in React component trees. For animations in utility functions, vanilla JS, or outside JSX, use the animate() function imported from motion (not motion/react). useAnimate is a React hook that provides a scoped imperative API for complex sequences inside components.
import { motion } from "motion/react"
const Card = () => <motion.div animate={{ opacity: 1 }} />
import { animate } from "motion"
animate("#box", { opacity: 1 }, { duration: 0.3 })
import { useAnimate } from "motion/react"
const [scope, animate] = useAnimate()