# Exit Animations

> Leave faster and quieter than you came.

- Section: Motion
- URL: https://critly.vercel.app/exit-animations
- Published: 2026-07-16
- Source: https://github.com/kausthubh-coder/critly/blob/main/content/motion/exit-animations.mdx

The lazy exit is the entrance played backwards: same distance, same
duration, same curve. It looks tidy, but it gives a goodbye as much time as a
hello. **Something leaving deserves less ceremony than something arriving.**

Dismiss the toasts and watch them go.

> **Interactive demo: Exit Animations.** Open https://critly.vercel.app/exit-animations to try it.

Both enter the same way, rising 12px over 240ms. The left one leaves by doing
that again in reverse. The right one fades out in half the time, 120ms, with
2px of blur, no travel and an ease-in.

An entrance has a job. Something new is here, and the motion shows you where
it came from. An exit has none. The user has already moved on, and anything
that makes them watch is in the way. So exits get about half the duration and
less movement. They are also the one place ease-in fits, since the element is
speeding away and nobody needs to watch it settle.

In React, an element removed from the tree is gone before it can animate.
Motion's `AnimatePresence` keeps it mounted until its `exit` animation
finishes.

## Exits that keep layout steady

The exit people forget most is a row leaving a list. Without one, the row
vanishes and everything below it jumps up in a single frame. Delete a few
tasks on each side.

> **Interactive demo: Exit List.** Open https://critly.vercel.app/exit-animations to try it.

On the right the row collapses its height while it fades, so the rows beneath
slide up instead of teleporting. It takes 160ms, short enough that you barely
notice it, and long enough that your eye can follow the list.

## Usage

**Motion**

```tsx
import { AnimatePresence, motion } from "motion/react";

<AnimatePresence initial={false}>
  {open && (
    <motion.div
      initial={{ opacity: 0, y: 12, filter: "blur(0px)" }}
      animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
      exit={{
        opacity: 0,
        filter: "blur(2px)",
        transition: { duration: 0.12, ease: "easeIn" },
      }}
      transition={{ duration: 0.24, ease: [0.23, 1, 0.32, 1] }}
    >
      Changes saved
    </motion.div>
  )}
</AnimatePresence>
```

**CSS**

```css
.toast {
  display: block;
  transition:
    opacity 240ms cubic-bezier(0.23, 1, 0.32, 1),
    translate 240ms cubic-bezier(0.23, 1, 0.32, 1),
    display 240ms allow-discrete;

  @starting-style {
    opacity: 0;
    translate: 0 12px;
  }
}

.toast[hidden] {
  display: none;
  opacity: 0;
  filter: blur(2px);
  transition:
    opacity 120ms ease-in,
    filter 120ms ease-in,
    display 120ms allow-discrete;
}
```

In CSS, the transition that runs is the one declared on the state you are
moving to. So the entrance timing lives on `.toast`, the exit timing lives on
`.toast[hidden]`, and `allow-discrete` holds off `display: none` until the
fade has finished.

## Resources

- [Details that make interfaces feel better](https://jakub.kr/writing/details-that-make-interfaces-feel-better): Jakub Krehel's collection of small motion decisions, including why exits should be subtler than entrances.
- [AnimatePresence](https://motion.dev/docs/react-animate-presence): How Motion keeps a removed React element mounted until its exit animation finishes.
- [@starting-style](https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style): The CSS rule that gives an element something to transition from when it first appears.
- [transition-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior): How allow-discrete lets an element fade out before display none removes it.
