Motion for React
motion.div, initial/animate/exit, AnimatePresence, variants and stagger, layout, scroll, springs.
Updated
What Motion is
Motion (formerly Framer Motion) is the most used animation library in React. The idea: you replace an HTML tag with its motion. version and describe the states, not the steps — Motion computes the animation between them.
npm i motion'use client'
import { motion } from 'motion/react'
export function Card() {
return <motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} />
}motion.* components are Client Components — the file has 'use client'. You can keep the page as a Server Component and animate only small pieces.
The basic props
| Prop | What it does |
|---|---|
initial |
the starting state (on mount) |
animate |
the target state — when it changes, Motion animates toward it |
exit |
the state on unmount (needs AnimatePresence) |
transition |
duration, ease, delay, or type: 'spring' with stiffness / damping / bounce |
whileHover, whileTap, whileFocus |
temporary states on interaction |
whileInView |
a state when the element enters the screen (+ viewport={{ once: true }}) |
layout |
automatically animates size / position changes in the layout |
layoutId |
two different elements with the same id "transform" into each other |
Exits: AnimatePresence
React removes an element from the DOM immediately when it's no longer rendered — there's no time for an animation. AnimatePresence keeps it until exit finishes:
<AnimatePresence>
{open && (
<motion.div key="panel" initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} />
)}
</AnimatePresence>Variants and stagger
Variants = named states, defined once and propagated to the children. Ideal for lists:
import { motion, stagger } from 'motion/react'
const list = { hidden: {}, show: { transition: { delayChildren: stagger(0.06) } } }
const item = { hidden: { opacity: 0, y: 16 }, show: { opacity: 1, y: 0 } }
<motion.ul variants={list} initial="hidden" whileInView="show" viewport={{ once: true }}>
{products.map(p => <motion.li key={p.id} variants={item}>{p.name}</motion.li>)}
</motion.ul>The children don't need initial / animate — they inherit the state names from the parent.
Layout animations — the "wow" effect in one line
<motion.div layout className={expanded ? 'w-full' : 'w-40'} /> // the size animates automatically{tabs.map(tab => (
<button key={tab} onClick={() => setActive(tab)} className="relative px-3 py-1">
{active === tab && <motion.span layoutId="tab-indicator" className="absolute inset-0 rounded-md bg-hover" />}
<span className="relative">{tab}</span>
</button>
))}The indicator "slides" from one tab to the next — without any position calculation.
Scroll
const { scrollYProgress } = useScroll() // 0 → 1 over the whole page
const scaleX = useSpring(scrollYProgress) // smoothed with a spring
<motion.div style={{ scaleX }} className="fixed inset-x-0 top-0 h-1 origin-left bg-accent" /> // a progress bar
const y = useTransform(scrollYProgress, [0, 1], [0, -200]) // parallaxuseTransform = interpolation: it maps an input range onto an output range.
Spring vs tween
Tween (duration + ease) |
Spring | |
|---|---|---|
| Duration | fixed | comes from physics |
| Feel | controlled, precise | natural, "alive" |
| Interrupted midway | can jump | continues smoothly, keeps its velocity |
| When | opacity, color, fades | movement, size, drag, gestures |
Summary
motion.div+initial/animate/exit;AnimatePresencefor exits.- Variants +
stagger()for lists;whileInViewfor reveals on scroll. layout/layoutIdfor layout transitions;useScroll+useTransformfor scroll; springs for movement.