Control React unmount timing with Framer Motion AnimatePresence
Keep exit animations playing before React removes conditional components from the DOM.
28 May 2026, 23:56 UTC

The problem: instant unmount skips exit animations
When React stops rendering a component, it removes the corresponding DOM node immediately. If that component is built with Framer Motion, any exit animation you defined never gets time to run. The element simply vanishes, which can feel abrupt for modals, toast notifications, or list items.
Useful takeaway
Wrap conditional or list JSX in AnimatePresence. Give the motion child an exit variant and a stable key. React keeps the element mounted until the exit transition finishes, then removes it from the DOM.
Worked configuration
This example toggles a dismissible panel. initial={false} skips the entrance animation on first render while preserving the exit animation for later removals.
import { AnimatePresence, motion } from "framer-motion";
export default function DismissiblePanel({ isVisible }) {
return (
{isVisible && (
This panel fades out before React removes it.
)}
);
}
What each part does
AnimatePresencewraps the conditional JSX and intercepts unmount requests.initial={false}prevents the entrance animation from playing when the component first mounts.exitdefines the values Framer Motion animates to before removal.key="panel"gives AnimatePresence a stable identity to track across renders.mode="popLayout"coordinates layout shifts among exiting siblings so remaining elements reflow smoothly instead of snapping.
How AnimatePresence delays unmounting
- React begins removing
motion.divbecauseisVisiblechanged to false. AnimatePresenceintercepts the unmount and keeps the DOM node in place.- Framer Motion plays the
exitvariant. - When the transition completes,
AnimatePresenceallows React to remove the node.
Only motion children with an exit animation are delayed. Plain HTML elements or non-motion components inside AnimatePresence unmount immediately.
Comparison: without and with AnimatePresence
| Scenario | Without AnimatePresence | With AnimatePresence |
|---|---|---|
| Conditional component hides | DOM node removed instantly | Exit variant plays, then node removed |
| First render | Entrance animation plays by default | Entrance skipped with initial={false} |
| List item removed | Sibling layout snaps immediately | Layout can reflow with mode="popLayout" |
Limitations and common mistakes
- Delayed cleanup: Because the component stays mounted during exit, timers, event listeners, and state updates remain active until the transition ends. A long exit can keep resources alive longer than expected.
- Non-motion children: Wrapping a plain
divinAnimatePresencedoes not delay its removal. Usemotion.divand declareexit. - Missing or unstable keys: Every exiting child needs a unique, stable
key. Without it, React may animate the wrong element or leave a node stuck in the exit state. - Key changes during exit: Changing a
keywhile a component is exiting breaks position interpolation between siblings and can freeze the exit animation. - Unwanted entrance animation: Omitting
initial={false}plays entrance animations when the component first appears, which is often unwanted for modals or panels already present on load.
Diagnostic checks
- Confirm the exiting element is a
motioncomponent, not a plain HTML element. - Confirm the element has an
exitprop or variant. - Confirm every exiting sibling has a unique
keythat does not change during exit. - Open browser developer tools, toggle visibility off, and inspect whether the DOM node remains while opacity or position changes.
Check the installed Framer Motion version
Run this in the project root:
npm list framer-motion
No special permissions are required. The output shows the installed version. Behavior and props such as mode can differ across major versions, so compare the result against the Framer Motion documentation for your installed major version before relying on mode="popLayout" or other version-specific behavior.
Practical verification
- Render the component with
isVisibleset to true. - Open browser developer tools and inspect the panel element in the DOM tree.
- Set
isVisibleto false. - Confirm the element remains in the DOM while its opacity changes.
- Confirm the element disappears only after the configured transition duration ends.
If the element vanishes immediately, the most likely cause is a missing key, a non-motion child, or an absent exit definition.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.