React's AnimatePresence modes explained

Matt Perry

In this tutorial, we're going to build the AnimatePresence modes example step-by-step.

This tutorial is rated beginner difficulty, which means we'll spend some time explaining the Motion APIs that we've chosen to use (and why), and also any browser APIs we encounter that might be unfamiliar to beginners.

Here's a live demo of the example we're going to be creating:

Loading...

Understanding AnimatePresence

When elements enter or leave the DOM in React, they typically just appear or disappear instantly. The AnimatePresence component from Motion gives you the power to animate these transitions, and the mode prop determines how those animations coordinate with each other.

Think of it this way: when you're swapping one element for another, should they animate at the same time? Should one wait for the other? Should the old element get out of the way immediately? Each scenario calls for a different mode.

Let's take a look at each and find out.

sync: Simultaneous animations

The default mode, "sync", animates elements in and out at the same time. There's no sequencing - as soon as an element is added or removed, its animation begins.

<AnimatePresence>
    <motion.div
        key={currentItem}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
    />
</AnimatePresence>

When to use sync mode

  • Cross-fade transitions where both elements should be visible simultaneously

  • Overlapping elements with position: absolute that don't affect each other's layout

  • Background images or full-screen transitions

  • Any situation where you want both animations happening at once

The catch with "sync" is that if your elements occupy the same space in the document flow, you'll see layout conflicts, with elements stacking or jumping. You'll need to handle positioning yourself, typically with position: absolute.

wait: Sequential animations

In "wait" mode, the entering element waits until the exiting element has fully animated out before it begins its entrance animation.

<AnimatePresence mode="wait">
    <motion.div
        key={step}
        initial={{ opacity: 0, x: 20 }}
        animate={{ opacity: 1, x: 0 }}
        exit={{ opacity: 0, x: -20 }}
    />
</AnimatePresence>

When to use wait mode

  • Multi-step wizards or onboarding flows

  • Tab content that should transition one at a time

  • Modal or dialog content changes

  • Any UI where you want to present information sequentially

A useful pattern with "wait" mode is using complementary easing functions. Set ease: "easeIn" on the exit animation and ease: "easeOut" on the enter animation - this creates an overall easeInOut feel across the combined transition.

<motion.div
    initial={{ opacity: 0 }}
    animate={{ opacity: 1, transition: { ease: "easeOut" } }}
    exit={{ opacity: 0, transition: { ease: "easeIn" } }}
/>

Note that "wait" mode only supports one child at a time. If you need to animate multiple children, consider "sync" or "popLayout".

popLayout: Instant reflow

The "popLayout" mode removes the exiting element from document flow immediately, allowing surrounding elements to reflow while the exit animation plays. The exiting element animates out from its "popped" position.

<AnimatePresence mode="popLayout">
    {items.map((item) => (
        <motion.li
            key={item.id}
            layout
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
        />
    ))}
</AnimatePresence>

When to use popLayout mode

  • Lists where items can be added or removed

  • Grid layouts with dynamic content

  • Any situation where remaining elements should immediately take their new positions

  • Interfaces where responsive reflow matters more than exit animation positioning

The "popLayout" mode pairs especially well with the layout prop. When you remove an item from a list, the remaining items smoothly animate to their new positions while the removed item fades out in place.

One important detail: when using "popLayout", any custom component that's an immediate child of AnimatePresence must be wrapped in React's forwardRef, forwarding the ref to the DOM element you want to pop from the layout.

const ListItem = forwardRef(function ListItem({ children }, ref) {
    return (
        <motion.li ref={ref} layout exit={{ opacity: 0 }}>
            {children}
        </motion.li>
    )
})

Choosing the right mode

Here's a quick decision guide:

Scenario

Recommended mode

Full-screen page transitions

sync with absolute positioning

Step-by-step wizard

wait

Tab content switching

wait

Dynamic list items

popLayout with layout

Image gallery with captions

sync for images, wait for captions

Notification toasts

popLayout

See it in action

The example above demonstrates all three modes side by side. Click the "Switch" button and observe how each mode handles the transition differently:

  • sync: Both circles animate simultaneously, briefly overlapping

  • wait: The old circle exits completely before the new one enters

  • popLayout: The exiting circle pops out of flow while animating out

Experiment with different animation properties and durations to see how each mode affects the overall feel of your transitions.

Motion is supported by the best in the industry.