---
title: Presence (Enter / Exit)
description: Animate elements as they're added and removed, like Motion's AnimatePresence.
---

## Overview

Mercury's `presence` is the equivalent of Motion's `<AnimatePresence>` — without the wrapper component. It's a Svelte transition, so it goes directly on the element whose mounting and unmounting you want to animate, and Svelte keeps the element in the page until its exit has played.

There are two ways to describe the animation:

- **Motion-style:** `initial`, `animate` and `exit` on the element's [`mercury()`](/docs/api/animate) attachment, with `presence` marking it as a presence child. This supports everything Motion's `exit` does — variants, `custom`, orchestrated children.
- **Shorthand:** `initial` and `exit` on `presence` itself, animating to and from the element's CSS. No attachment needed.

## Motion-style

```svelte
{#if show}
	<div
		transition:presence
		{@attach mercury({
			initial: { opacity: 0, scale: 0.8 },
			animate: { opacity: 1, scale: 1 },
			exit: { opacity: 0, scale: 0.8 },
			transition: { type: 'spring', bounce: 0.2 }
		})}
	/>
{/if}
```

This maps one-to-one from Motion:

```jsx
// motion/react
<AnimatePresence>
	{show && (
		<motion.div
			initial={{ opacity: 0, scale: 0.8 }}
			animate={{ opacity: 1, scale: 1 }}
			exit={{ opacity: 0, scale: 0.8 }}
			transition={{ type: 'spring', bounce: 0.2 }}
		/>
	)}
</AnimatePresence>
```

The exit runs through Motion's animation state, so it can be a [variant](/docs/api/variants), and children with variants exit with it — staggered, or before or after it with `when`:

```svelte
<script lang="ts">
	const menu: Variants = {
		open: { opacity: 1, transition: { staggerChildren: 0.05 } },
		closed: { opacity: 0, transition: { when: 'afterChildren', staggerChildren: 0.03 } }
	};
	const item: Variants = { open: { opacity: 1, x: 0 }, closed: { opacity: 0, x: -16 } };
</script>

{#if open}
	<ul
		transition:presence
		{@attach mercury({ variants: menu, initial: 'closed', animate: 'open', exit: 'closed' })}
	>
		{#each items as label (label)}
			<li {@attach mercury({ variants: item })}>{label}</li>
		{/each}
	</ul>
{/if}
```

Svelte removes the element as soon as the whole exit — children included — has finished.

### Exit variants and `custom`

An exiting element no longer receives new props, but its exit may depend on state that changed since — like which way a slideshow is going. As with `<AnimatePresence custom>`, the `custom` on `presence` is passed to the exit's dynamic variants:

```svelte
<script lang="ts">
	const slide: Variants = {
		enter: (dir: number) => ({ x: dir * 120, opacity: 0 }),
		center: { x: 0, opacity: 1 },
		exit: (dir: number) => ({ x: dir * -120, opacity: 0 })
	};
</script>

{#key index}
	<div
		transition:presence={{ mode: 'popLayout', custom: direction }}
		{@attach mercury({
			custom: direction,
			variants: slide,
			initial: 'enter',
			animate: 'center',
			exit: 'exit'
		})}
	>
		{slides[index]}
	</div>
{/key}
```

<Component path="slideshow" />

## Shorthand

Without an attachment, describe the hidden state on `presence`:

```svelte
{#if show}
	<div
		class="card"
		transition:presence={{
			initial: { opacity: 0, y: 25 },
			exit: { opacity: 0, y: 25 },
			transition: { duration: 0.3 }
		}}
	>
		Hello
	</div>
{/if}
```

- **`initial`** — the hidden state the element enters **from**. It animates from these values to its own CSS styles.
- **`exit`** — the values the element animates **to** before it's removed.
- **`transition`** — shared by both directions. Each side can override it with its own `transition`, e.g. `exit: { opacity: 0, transition: { duration: 0.15 } }`.

Your CSS stays the resting state: once an enter finishes, Mercury hands the animated properties back to the element's styles, so an element that entered from `height: 0` keeps following its content, and an `opacity` class keeps applying. Transform values (`x`, `y`, `scale`, `rotate`…) settle at their identity.

For simple cases, skip `initial`/`exit` and pass the hidden state directly. Enters animate **from** it, exits **to** it:

```svelte
<div transition:presence={{ opacity: 0, y: 25 }} />

<!-- or per direction -->
<div in:presence={{ opacity: 0 }} out:presence={{ opacity: 0, y: 25 }} />
```

## Options

These go on `presence` with either style:

- **`mode`** — `'sync' | 'wait' | 'popLayout'`, matching `<AnimatePresence mode>`.
- **`custom`** — data for the exit's dynamic variants (see above).
- **`onExitComplete`** — called once the exit has finished, right before the element is removed.

```svelte
<div transition:presence={{ onExitComplete: () => console.log('gone') }} />
```

## First render (`initial={false}`)

Svelte transitions are _local_ by default: they don't play when an **ancestor** block mounts, only when the element's own block is added or removed. For the shorthand that's exactly `<AnimatePresence initial={false}>`; opt in to Motion's default with `|global`:

```svelte
<div transition:presence|global={{ initial: { opacity: 0 }, exit: { opacity: 0 } }} />
```

With the Motion-style attachment, `initial` plays whenever the element mounts, like a `motion.div`. Use `initial: false` to skip it.

## Modes (`sync`, `wait` and `popLayout`)

Quoting [Motion's documentation](https://motion.dev/docs/react-animate-presence#mode):

> - “sync”: Children animate in/out as soon as they’re added/removed.
> - “wait”: The entering child will wait until the exiting child has animated out. Note: Currently only renders a single child at a time.
> - “popLayout”: Exiting children will be “popped” out of the page layout. This allows surrounding elements to move to their new layout immediately.

```svelte
{#key currentTab}
	<section
		transition:presence={{
			initial: { opacity: 0, scale: 0.9 },
			exit: { opacity: 0, scale: 0.9 },
			mode: 'popLayout',
			transition: { duration: 0.25 }
		}}
	>
		{currentTab.content}
	</section>
{/key}
```

- **`mode: 'sync'`** (default) — entering and exiting elements animate at the same time.
- **`mode: 'wait'`** — the entering element stays out of the page (`display: none`) until the exiting one has animated out and been removed, then animates in. Mercury coordinates this for `presence` and `mercury()` enters under the same parent element.
- **`mode: 'popLayout'`** — the exiting element is pinned where it is with `position: absolute` at its measured size and offset, so siblings reflow immediately while it animates out. Its parent becomes `position: relative` while any child is popped, if it was `static`.

## Interruptions

Presence animations are interruptible, like Motion's:

- If an element is re-inserted while its exit is still running (a quick toggle back), it animates from wherever the exit left off back to its resting state, instead of replaying the enter.
- If an exit starts mid-enter, it animates from the current state.

:::note
`presence` respects the user's `prefers-reduced-motion` setting: the shorthand adds and removes elements without animating, and Motion-style exits follow Mercury's [reduced motion](/docs/api/configuration#reduced-motion) rules.
:::

## Caveats & differences from Motion

`presence` is built directly on Svelte transitions instead of a wrapper component. The common cases map over cleanly, but a few rules come from Svelte's model rather than Mercury and are worth knowing up front.

### Exits inside a conditional parent need `|global`

Svelte does **not** play a _local_ `out:` transition when a **parent** block (`{#if}`, `{#key}`, `{#each}`) is the thing being removed — it only plays local transitions when the element's own block is removed. If an ancestor block is destroyed, add the `|global` modifier so the exit still runs:

```svelte
{#if open}
	{#if state === 'success'}
		<Success />
	{:else}
		<!-- the parent {:else} block is destroyed on success, so |global is required -->
		<form out:presence|global={{ opacity: 0, y: 8, mode: 'popLayout' }}>…</form>
	{/if}
{/if}
```

Rule of thumb: if the element's own `{#if}` toggles, local `presence` is fine; if an ancestor block is removed, use `presence|global`.

### `exit` needs `presence`

Like Motion's `exit` outside `<AnimatePresence>`, an `exit` on the attachment does nothing unless the element has `transition:presence` or `out:presence`: without a transition, Svelte removes the element immediately.

### `mode` is set per element, not on a wrapper

Motion sets `mode` once on `<AnimatePresence>`. In Mercury the mode lives on each element's `presence`. For a list, set the same `mode` on every item.

### `popLayout` reflows siblings instantly

Motion pairs `popLayout` with the `layout` prop so neighbours _animate_ to their new positions. Mercury's `popLayout` pops the exiting element out of flow; for a smooth reflow, add Mercury's [`layout`](/docs/api/layout-animations) to the siblings.

### `wait` coordinates through the shared parent

`wait` sequences the next enter after the current exit through the **shared parent element**. The entering and exiting elements must live under the same DOM parent — keyed swaps (`{#key}`) and `{#if}` / `{:else}` blocks normally satisfy this.
