one element → many elements: the three primitives
framer_motion_core animates a single element with a requestAnimationFrame tween.
Real UIs need choreography: a list ripples in one-by-one, a dismissed card
fades out before it leaves the DOM, a parent flips a label and every child follows. Framer Motion ships
three primitives for this — and each is a plain-React mechanism in disguise:
| primitive | what it does | the plain-React mechanism (used here) |
|---|---|---|
variants |
named target objects (hidden, visible, exit) — parent switches the label, children inherit it |
a React.Context carries the active label + stagger step down the tree |
stagger |
children animate with a sequential delay — a wave, not a flash | each child reads its index and sets transitionDelay = index * step |
AnimatePresence |
keeps a removed child mounted long enough to play its exit animation | park the id in an "exiting" map, render it with the exit target, splice after N ms |
The demo below implements all three from scratch — no motion/react
import. Items enter with a stagger, the list label flips hidden → visible on mount, and the
Remove button parks the row in an exit state for 300ms before the real unmount.
1 · the orchestrator you write (edit me)
2 · Babel compiles JSX → element tree
<VariantContext.Provider> becomes
React.createElement(VariantContext.Provider, …). The Context value flows down;
each MotionItem reads it via useContext and computes its own delay.
// (hit "compile & render" to see Babel's output)
3 · live React (variants + stagger + exit, proven)
On mount the list label flips hidden → visible and items ripple in with a
0.08s stagger. Click Add to append; click
✕ to remove — the row slides out over 300ms, THEN unmounts. The
gold-check automates this: assert 3 → add → assert 4 → remove #1 →
wait for exit → assert 3 remain and #1 is gone.
items: — · gold: 3 → +Add → 4 → remove #1 → wait exit → 3 remain, #1 gone
intent → orchestration primitive
| intent | primitive | why |
|---|---|---|
| parent flips a label, children follow | variants + a Context |
one switch animates N children — no per-child prop drilling |
| list ripples in one-by-one | staggerChildren / per-index transitionDelay |
wave effect; each child waits index * step |
| removed element animates out first | AnimatePresence / defer unmount |
React unmounts instantly; you must hold the node for the exit duration |
| pause before children start | delayChildren (no stagger) |
all children wait ONE delay, then animate together |
| parent finishes before children start | when: 'beforeChildren' |
strict ordering — parent then children, not overlapping |
| different timing per variant | transition inside each variant target |
visible can spring, exit can tween — independent |