<!-- Source: https://motion.svelte.page/examples/use-presence -->

# usePresence

> Custom exit animations from inside the child. PresenceChild holds the component rendered until your safeToRemove() fires.

**Source:** [https://motion.svelte.page/examples/use-presence](https://motion.svelte.page/examples/use-presence)

**Markdown mirror:** [https://motion.svelte.page/examples/use-presence.md](https://motion.svelte.page/examples/use-presence.md)

---

This mirror preserves the prose, implementation notes, and runnable Svelte source behind the live example page.

## FIG-001: custom exit via usePresence.

`<PresenceChild present={…}>` keeps the card rendered after `present` flips false. Inside, `usePresence` hands the card a `safeToRemove` callback — the card runs its own CSS transition and releases the wrapper on `transitionend`.

**Metadata:** tag: `ANIMATE-PRESENCE` | pattern: `PresenceChild + safeToRemove`

### Notes

- `PresenceChild` holds the child rendered while the surrounding `AnimatePresence` is exiting it. The card stays in the DOM with `isPresent === false` until `safeToRemove()` fires.
- Inside the card, `usePresence()` returns `[isPresent, safeToRemove]`. The effect listens for `transitionend` on the card node and calls `safeToRemove` once the CSS transition completes — fully custom exit under the component's own control.
- Re-entering (clicking Show mid-exit) cancels the hold; the stale `safeToRemove` becomes a no-op so a late `transitionend` can't tear down a now-present card. `onExitComplete` on the parent tallies finished exits.

### Source

#### Default.svelte

Source file: [src/lib/examples/use-presence/demos/Default.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/use-presence/demos/Default.svelte)

```svelte
<script lang="ts">
    import { AnimatePresence, PresenceChild, motion, styleString } from '@humanspeak/svelte-motion'
    import { onMount } from 'svelte'
    import UsePresenceCard from './UsePresenceCard.svelte'

    // Custom exit driven from the child: `<PresenceChild present={…}>` holds the
    // card rendered after `present` flips to `false`. Inside the card, `usePresence`
    // returns `[false, safeToRemove]` once the hold begins — the card runs its own
    // CSS transition and calls `safeToRemove()` on `transitionend` so the wrapper
    // releases it.
    let visible = $state(true)
    let exitsCompleted = $state(0)

    // Defer the AnimatePresence subtree until after hydration — SSR doesn't have a
    // window to register presence children against.
    let mounted = $state(false)
    onMount(() => (mounted = true))
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// use-presence</span>
            <span class="micro state">
                exits: {String(exitsCompleted).padStart(2, '0')}
            </span>
        </div>

        <div class="stage">
            {#if mounted}
                <AnimatePresence onExitComplete={() => exitsCompleted++}>
                    <PresenceChild present={visible}>
                        <UsePresenceCard />
                    </PresenceChild>
                </AnimatePresence>
            {/if}
        </div>

        <div class="strip-foot">
            <motion.button
                type="button"
                onclick={() => (visible = !visible)}
                whileHover={{ scale: 1.04 }}
                whileTap={{ scale: 0.96 }}
                transition={{ type: 'spring', stiffness: 500, damping: 30 }}
                style={styleString(() => ({
                    fontFamily: 'var(--brut-mono, monospace)',
                    fontSize: '0.6875rem',
                    textTransform: 'uppercase',
                    letterSpacing: '0.08em',
                    border: '1px solid var(--brut-accent, #247768)',
                    backgroundColor: 'var(--brut-accent-soft, rgba(36, 119, 104, 0.1))',
                    color: 'var(--brut-accent, #247768)',
                    padding: '0.5rem 0.875rem',
                    cursor: 'pointer'
                }))}
            >
                {visible ? '– hide card' : '+ show card'}
            </motion.button>
            <span class="micro">exit: css transition → safeToRemove()</span>
        </div>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 1.5rem;
        min-height: 360px;
    }

    .strip {
        width: 100%;
        max-width: 420px;
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .micro {
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .state {
        color: var(--brut-accent, #247768);
        font-variant-numeric: tabular-nums;
    }

    .strip-head,
    .strip-foot {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .strip-foot {
        border-bottom: none;
        border-top: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-top: 0.75rem;
        padding-bottom: 0;
    }

    .stage {
        height: 8rem;
        display: flex;
        align-items: center;
        justify-content: center;
    }
</style>
```

#### UsePresenceCard.svelte

Source file: [src/lib/examples/use-presence/demos/UsePresenceCard.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/use-presence/demos/UsePresenceCard.svelte)

```svelte
<script lang="ts">
    import { usePresence } from '@humanspeak/svelte-motion'

    const presence = $derived(usePresence())
    const isPresent = $derived(presence[0])
    let node: HTMLElement | undefined = $state()

    $effect(() => {
        const [present, safeToRemove] = presence
        if (present || !node || !safeToRemove) return
        const el = node
        const onEnd = (e: TransitionEvent) => {
            if (e.target !== el) return
            safeToRemove()
        }
        el.addEventListener('transitionend', onEnd, { once: true })
        return () => el.removeEventListener('transitionend', onEnd)
    })
</script>

<div bind:this={node} class="card" class:exiting={!isPresent} data-is-present={isPresent}>
    <span class="card-label">// usePresence()</span>
    <strong class="card-value">{isPresent ? 'present' : 'exiting'}</strong>
</div>

<style>
    /* The exit is driven by THIS CSS transition — `usePresence` hands back
       `safeToRemove`, which the card calls on `transitionend`. Keep the
       transition intact; it is the API being demonstrated. */
    .card {
        width: 220px;
        display: flex;
        flex-direction: column;
        gap: 0.375rem;
        padding: 1rem 1.125rem;
        border: 1px solid var(--brut-ink, #0a0a0a);
        background: var(--brut-bg-2, #eef4f1);
        box-shadow: 6px 6px 0 var(--brut-rule, #d6dedb);
        opacity: 1;
        transform: translateY(0);
        transition:
            opacity 300ms ease,
            transform 300ms ease;
    }

    .card.exiting {
        opacity: 0;
        transform: translateY(-12px);
    }

    .card-label {
        font-family: var(--brut-mono, monospace);
        font-size: 0.625rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .card-value {
        font-family: var(--brut-mono, monospace);
        font-size: 0.875rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--brut-accent, #247768);
    }
</style>
```
