Framer Motion AnimatePresence: Diagnosing Skipped Exit Animations
A conditional child inside AnimatePresence unmounts instantly without playing its exit animation. Use this diagnostic guide to isolate the cause and apply the correct fix.
08 Jan 2026, 06:56 UTC

The problem and what to check first
A conditional motion child inside Framer Motion’s AnimatePresence disappears instantly when its parent removes it, even though the child defines an exit prop or exit variant. No fade, slide, or collapse plays; the DOM node is simply gone.
The useful takeaway is that AnimatePresence does not animate every disappearing element automatically. It coordinates exit animations only for direct motion children that remain in the React tree long enough to finish the transition. If the wrapper unmounts, keys are unstable, or the child lacks an explicit exit declaration, React removes the node before Framer Motion can animate it.
Recognizable condition
You are likely facing this issue when all of the following are true:
- A
motion.divor similar element is rendered conditionally insideAnimatePresence. - The child defines
exit, an exit variant, or relies onexitBeforeEnterfor exit ordering. - Toggling the parent condition removes the element from the DOM instantly, with no visible transition.
Cause and diagnostic table
| Observed condition | Likely cause | Quick check |
|---|---|---|
| Exit prop exists, but animation skips entirely | AnimatePresence itself is conditionally unmounted | Confirm AnimatePresence remains mounted while the child toggles |
| No exit animation on list items or conditional branches | Missing or unstable React key props | Check that each toggled child has a stable, unique key |
| Exit and enter animations overlap or skip unexpectedly | mode set to sync or popLayout instead of wait | Inspect the mode prop on AnimatePresence |
Custom wrapper around motion never exits | Wrapper does not forward ref or does not render motion directly | Verify the wrapper passes ref to the underlying motion element |
| Exit fails after a route change or parent state update | An ancestor unmounts AnimatePresence while a child is exiting | Inspect the component tree during the toggle; confirm no ancestor removes the wrapper mid-exit |
Ordered checks
1. Verify AnimatePresence stays mounted
AnimatePresence must remain in the React tree while its child performs the exit animation. If the wrapper is conditionally rendered—for example, {isOpen && ...}—React unmounts the wrapper and child together before the exit can run.
Move the condition inside the wrapper so AnimatePresence is always present, and toggle only the child:
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
2. Confirm the motion child declares an exit value
AnimatePresence looks for an exit prop or an exit variant on the direct motion child. If neither exists, the child unmounts immediately. The exitBeforeEnter prop controls whether exiting children finish before entering children mount; it does not replace the need for an exit declaration.
Add an explicit exit object:
<motion.div
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.2 }}
/>
3. Ensure stable keys for entering and exiting children
React uses key to distinguish an exiting node from a newly entering one. Without stable keys, AnimatePresence may treat a toggled child as a replacement and skip the exit animation. Assign a static key to each conditional branch or list item. Do not use array indices or randomly generated values, because those change when the list reorders.
4. Check the mode prop on AnimatePresence
The mode prop controls how exiting and entering children coordinate. wait keeps exiting children mounted until their exit completes before mounting entering children. sync runs entering and exiting animations simultaneously. popLayout enables layout-aware crossfades. If exiting children disappear as soon as a replacement mounts, confirm mode is set to wait.
5. Check ref forwarding through custom components
If you wrap motion in a custom component, Framer Motion needs a ref to the underlying element for layout measurement and some animation features. A simple opacity exit may still work without a forwarded ref, but a broken ref chain can prevent custom components from registering with AnimatePresence correctly.
Forward the ref:
const Box = React.forwardRef(({ children, ...props }, ref) => (
<motion.div ref={ref} {...props}>
{children}
</motion.div>
));
6. Confirm no parent unmounts AnimatePresence mid-exit
React removes subtrees synchronously when a parent unmounts. If a layout effect, route change, or state update in an ancestor removes AnimatePresence while a child is still exiting, the child is torn down immediately and no exit animation plays. Keep AnimatePresence higher in the tree than the conditional logic, or defer the parent unmount until after the exit completes.
Fixes tied to findings
- Missing exit: Add
exit={{ opacity: 0 }}or an exit variant to themotionchild. - Wrapper unmounting: Keep
AnimatePresencemounted and toggle the child with a condition and key. - Unstable keys: Replace index-based or missing keys with stable identifiers.
- Wrong mode: Set
mode="wait"when exiting children must finish before replacements mount. - Broken ref chain: Wrap custom components with
React.forwardRefand pass the ref tomotion. - Premature parent unmount: Lift
AnimatePresencehigher in the tree or defer ancestor unmounting until after the exit completes.
Limitations and practical verification
AnimatePresence delays actual DOM unmounting until the exit animation finishes. That means focus, layout space, and event listeners can persist longer than expected. If the exiting element contains focusable inputs or overlays the page, users may still interact with it during the transition.
Behavior can differ between Framer Motion major versions. Older versions may handle mode, keys, or exit ordering differently. Check your installed version’s documentation before applying a fix written for another release.
To verify a fix without relying on anecdotal output:
- Add a toggle that renders a
motion.divinsideAnimatePresencewithexit={{ opacity: 0 }}. - Open React DevTools during unmount and confirm
AnimatePresencestays mounted while the child is marked as exiting. - Inspect the DOM and confirm the node remains until the transition finishes, then is removed.
- Repeat the test with
mode="sync"to observe how entering and exiting children overlap.
Escalation criteria
If the exit animation still fails after you confirm AnimatePresence placement, stable keys, an explicit exit prop, and correct ref forwarding, reproduce the issue in a minimal project. If it persists there, suspect a version-specific regression or documentation gap. Test an adjacent major version of Framer Motion in a sandbox before changing the production dependency.