Shots in this scene
AnimatePresence and Exits
In one line
React removes an element the moment it leaves the tree, so there is nothing left to animate. AnimatePresence keeps the element in the page until its exit animation ends, and its mode decides whether entering and leaving elements overlap.
Think of it like this
A waiter clearing a table. If the plate vanished the instant you stood up, you would never see it go. A good waiter waits until you are done, then takes it away. AnimatePresence is the waiter: React says "remove this", and the plate stays until the exit is finished.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- AnimatePresence
- A Motion component that keeps removed children in the page until their
exitanimation finishes. - exit
- The target a
motionelement animates to when it is removed. - key
- React's identity tag for list items.
AnimatePresenceuses it to know which child left. - mode
sync,waitorpopLayout: whether entering and leaving elements overlap, take turns, or leave the layout at once.- presence
- Whether a child is still wanted by React. A leaving child is not present but is still in the page.
- safeToRemove
- A function from
usePresencethat a component calls to tellAnimatePresenceits own exit is done.
Step by step
01See why React cannot do it alone
React compares the old list with the new one and deletes the DOM node of any item that is gone, in the same commit. Nothing is left for an exit to move.
02Remove an item
Run the first example. At 300 ms the page removes Lights by itself. Pause the Timeline during the fade: two li elements are in the page, one at half opacity. The console says exit complete, 2 in the DOM just before the node goes.
03Switch the mode
Choose popLayout, press Add, and remove an item: the leaving row is position: absolute while it fades, so the rest close up at once. With sync it keeps its place until it is gone.
04Take the key away
In the second example the page with no key swaps text instantly. Only the keyed copy fades and logs exit complete: keyed.
Try it yourself
- 1
Compare the three modes
In the first example, change the mode to
wait,popLayoutandsync, and add an item right after removing one. Measured 100 ms after the page removesLights, with a new item added 70 ms before that reading: withsyncandpopLayoutthe leaving row and the new row are both in the page (popLayouthas the leaving row atposition: absolute); withwaitonly the leaving row exists, and the new one waits for it.waitis meant for one child at a time, so with a list it also prints the "odd visual behaviour" warning andLockis not on screen either. - 2
Use the index as the key
Copy the keyed list from the data (
Lock,Lights,Bell) and usekey={index}. RemoveLock. Measured 150 ms later: the page showsLights,Belland a fadingBell. The row that left the data is not the one that fades.
Code & diagrams
Add, remove with x, and change the mode. At 300 ms the page removes Item 2 for you. The console reports how many `li` were in the page when the exit finished.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
exit complete, 2 in the DOMBoth rows swap Specs for Gallery. Only the keyed one fades out and in, one after the other (`wait`); the other changes at once.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
exit complete: keyedThe row is removed, then returned 100 ms later while still fading. It comes back and its exit never completes.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
Lock isPresent=true
Lock isPresent=false
Lock isPresent=true
in the DOM: 1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Remove the key from a swapped child
In the second example's keyed branch, delete key={i}.
Myth vs fact
Myth
Wrap any component in AnimatePresence and its inner motion elements will exit.
Fact
The element that is removed must be a direct child of AnimatePresence with a stable key. Elements inside it can then run their own exit.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The leaving child is rendered from a saved copy of the old element, so it shows the *old* props. Do not expect it to react to state that changed after it started leaving.
- ▸
Exit animations delay DOM removal. A long exit on a modal keeps its focus trap and scroll lock alive for as long: set the lock off on
onExitComplete, or make the exit short. - ▸
mode="popLayout"measures the leaving element and pins it; a parent withoutposition: relativemakes it jump relative to the wrong box. The leaving row also stops taking part in flex or grid flow immediately, so its siblings animate withlayout(Topic 15.4).
Remember this
- 1
The problem this solves:
{show && <Panel />}makes the panel disappear in one frame, because React deletes its DOM node in the same commit. CSS cannot animate a node that no longer exists. Without help, enter animations are easy and exit animations are impossible. - 2
How it works.
AnimatePresenceremembers its children from the previous render. When a child with akeyis no longer returned, it keeps rendering it, tells it (through context) that it is no longer present, and the child'sexitprop animates. When every exit animation of that child has finished,AnimatePresencedrops it and callsonExitComplete. The callback runs just before the removal: in the first example it reports2 in the DOMwhen one of two items has finished leaving (measured). - 3
Keys decide everything.
AnimatePresencetells children apart bykey. A list needs a stable id as key. With no key, switching content from one page to another is the same element to React, so nothing leaves and nothing enters: in the second example the unkeyed text changes toGalleryat once while the keyed one fades out first and callsonExitComplete. With index keys, removing the first of three items makes the *last* item play the exit, and the other rows change text (measured:Bellfaded whileLockwas removed from the data). - 4
Modes.
sync(the default) lets entering and leaving elements run together, so the layout can briefly hold both.waitlets one element leave, and only then does the next enter; it is built for one child at a time, and with several children Motion logsYou're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.(measured).popLayouttakes the leaving element out of the layout at once (measured: its computedpositionwasabsolutewhile it faded), so its siblings move into place immediately; the parent must be positioned. - 5
Rapid changes. Put an element back while it is still leaving and it returns:
usePresence()reportsisPresent: falseand thentrueagain, the component is not re-created, and its exit never completes (measured in the third example: no "exit done", and oneliin the page). - 6
Presence hooks.
useIsPresent()tells a component it is leaving, for example to disable its buttons.usePresence()returns[isPresent, safeToRemove]; a component that drives its own exit (a GSAP timeline, a canvas fade) callssafeToRemove()when done.initial={false}onAnimatePresenceskips the enter animation of children present on the first render, which the configurator uses. - 7
Accessibility. A leaving element is still in the page while it exits, so keep exits short (about 150 to 250 ms), never block focus or input on them, and remember that under reduced motion Motion makes position and scale changes instant but still animates opacity (Topic 15.6): an
exit={{ opacity: 0, x: 40 }}becomes a plain fade.
Explain it without notes
Why can CSS alone not animate an element that React removes, and what does AnimatePresence change?
What happens when you remove the key, or use the array index as the key?
Practice
Make a toast that slides up 16 px and fades in, and fades out when dismissed. Show it for one toast at a time.
A cart row must not be clickable while it is leaving. Write the row.
Done when you can
You can explain why an exit needs
AnimatePresenceYou can choose
sync,waitorpopLayoutfor a case and say what it changesYou can say what a missing key or an index key does to exits
You can handle an element that returns while it is leaving