AnimatePresence
Animate elements out before Svelte removes them from the DOM.
Svelte removes elements from the DOM as soon as an {#if} or {#each} block changes. AnimatePresence coordinates with Svelte's outro system so motion children can run their exit animation first.
hello
<script>
import { motion, AnimatePresence } from "motion-start";
let visible = $state(true);
</script>
<AnimatePresence>
{#if visible}
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.6 }}
/>
{/if}
</AnimatePresence>
Props
mode
type: "sync" | "wait" | "popLayout"
`sync` uses Svelte's normal outro layout. `wait` finishes the exit animation before the next element
enters. `popLayout` removes exiting elements from the layout flow while keeping their measured
visual position, so siblings reflow immediately.
default: "sync"
initial
type: boolean
Set to `false` to disable the mount animation for children present on first render.
default: true
custom
type: unknown
Data passed to dynamic `exit` variants, so an element can animate out differently depending on
context.
presenceAffectsLayout
type: boolean
Whether presence changes trigger layout projection in sibling `layout` components.
default: true
onExitComplete
type: () => void
Called once every exiting element in the current batch has finished animating.
Keyed lists
Inside {#each}, use a keyed block so identity is stable across updates.
<AnimatePresence mode="popLayout">
{#each items as item (item.id)}
<motion.li
layout
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
>
{item.label}
</motion.li>
{/each}
</AnimatePresence>
Presence hooks
usePresence and useIsPresent let a child read its own presence state — useful when the exit
animation isn't driven by an exit prop.
<script>
import { usePresence } from "motion-start";
const [isPresent, safeToRemove] = usePresence();
</script>
usePresenceData reads the custom value from the nearest AnimatePresence.
Migrating
presenceAffectsLayout was a no-op before 0.1.21 and now behaves as documented. If you relied on
the old behaviour, set it to false explicitly.