Presence stack
<floating-presence-stack> is the package’s declarative presence component.
Use it for toasts, notices, and other entries that need a shared limit,
timeouts, pause reasons, exit presence, and eventual removal.
It is headless: it does not decide color, peek spacing, or ARIA wording. It does own the core host box that pointer hit-testing depends on. The application owns template markup, stacking layout, close controls, and in-flow vs top-layer opt-out.
Clones default to native popover="manual". Use top-layer="none" when the
stack belongs inside an application-owned layout and should be clipped or
scrolled by its parent. Toasts are not modal content; do not use a dialog for
them.
Basic stack
Section titled “Basic stack”<floating-presence-stack id="notices" tabindex="-1"> <template slot="content"> <article class="toast" role="status"> <strong data-presence-text="title"></strong> <p data-presence-text="description"></p> <button type="button" data-presence-close>Dismiss</button> </article> </template></floating-presence-stack>
<script type="module"> const notices = document.querySelector('#notices'); notices.add({ title: 'Saved', description: 'Your changes are ready.', });</script>Configuration
Section titled “Configuration”configure() accepts the options that normally change together. Static
attributes cover the same values for markup-owned configuration.
| Option | Purpose |
|---|---|
limit |
Maximum number of open entries; defaults to 3 |
timeout |
Milliseconds before automatic close; use 0 to keep entries open |
exitDuration |
Time reserved for the exit-presence phase. 0 (default) reads the consumer CSS discrete display / overlay length |
topLayer |
Defaults to 'popover'. Use 'none' for in-flow stacks. 'dialog' only when the surface is genuinely modal |
pauseOn |
Space-separated pointer and/or focus. Empty (default) does not bind host pause |
resumeDelay |
Delay before resume('pointer') after pointerleave; defaults to 100 |
The equivalent limit, timeout, exit-duration, top-layer, pause-on,
and resume-delay attributes are available for static HTML. JavaScript
options take precedence.
The host is a generating box (display: block), not display: contents.
Empty stacks use pointer-events: none so the page stays clickable; open or
paused stacks raise the host to auto so gaps between popovers do not fall
through. Clone surfaces get pointer-events: auto so buttons stay clickable
while the empty host box is none.
Paused min-height reads --floating-presence-hit-span from application CSS.
Size that token to the current expanded stack — card height plus a fixed
row offset times count - 1 — not to a maximum cap. Collapsed hosts can keep
height: 0 so only the cards themselves receive hits. The application also
owns paused translate: keep a fixed row (84px in the Toast demo) so later
cards do not cover earlier content.
Text and controls
Section titled “Text and controls”data-presence-text writes values from the entry into matching descendants.
It supports a value path and the $id, $index, and $remaining placeholders.
Use data-presence-close on a button inside the rendered entry. The first
template root receives runtime markers such as data-status,
data-presence-id, data-presence-index, and data-presence-overflowed,
plus --floating-presence-index. The host sets --floating-presence-count
for visible open entries. Do not use sibling-count() as the count source on
Web Components; the <template> sibling is included.
The host reflects pause state with data-presence-paused, open entries with
data-presence-open, and emits presencechange with the current snapshot.
Pause and resume
Section titled “Pause and resume”Pause is opt-in. Set pause-on="pointer focus" to bind the host, or call
pause() / resume() yourself. Named reasons overlap, so every reason must
be resumed before the timeout continues:
notices.pause('pointer');notices.pause('focus');
notices.resume('pointer');notices.resume('focus');Pointer leave waits for resume-delay so moving across the gap between
stacked popovers does not fold the stack. A pointerenter in that window
cancels the pending resume. Focus out keeps the pause while
contains(activeElement) is true.
Vue adapter
Section titled “Vue adapter”Vue uses useFloatingPresenceStack() with the same core options and actions,
but the template owns every rendered surface. records, snapshot, and
paused are reactive refs; add, close, remove, pause, resume, and
subscribe mirror the shared context. Pass pauseTarget to bind the same
opt-in pause listeners and --floating-presence-count token; the composable
does not style that node.
<script setup lang="ts">import {ref} from 'vue';import {useFloatingPresenceStack} from '@floating-ui-plus/vue';
type Notice = {title: string; description?: string};
const viewport = ref<HTMLElement>();const notices = useFloatingPresenceStack<Notice>({ limit: 3, timeout: 5000, pauseTarget: viewport, pauseOn: 'pointer focus',});
function addNotice() { notices.add({title: 'Saved', description: 'Your changes are ready.'});}</script>
<template> <button type="button" @click="addNotice">Show notice</button>
<div ref="viewport" class="toast-viewport" :data-presence-paused="notices.paused.value || undefined" > <article v-for="(record, index) in notices.records.value" :key="record.id" class="toast" :data-presence-id="record.id" :data-presence-index="index" :data-status="record.open ? 'open' : 'close'" :data-presence-overflowed="record.overflowed || undefined" :style="{'--floating-presence-index': index}" role="status" @transitionend="!record.open && notices.remove(record.id)" > <strong>{{ record.value.title }}</strong> <p v-if="record.value.description">{{ record.value.description }}</p> <button type="button" @click="notices.close(record.id)">Dismiss</button> </article> </div></template>Keep a closed record rendered until its exit transition finishes, then call
remove(record.id). Do not use v-if="record.open" on the surface itself or
the exit state will be removed before it can run.
For a fixed Vue surface that should use native Popover or dialog behavior, pair
the rendered element with useFloatingTopLayer(). That hook defaults to
Popover and manages only the native lifecycle; it does not position or style
the element. For custom in-flow exit motion, use FloatingTransition or
useFloatingTransition().
Shared controller contract
Section titled “Shared controller contract”FloatingPresenceStack and createFloatingPresenceStack() are also exported
from @floating-ui-plus/web. Both adapters use this record shape:
| API | Contract |
|---|---|
limit |
Maximum number of simultaneously open entries; defaults to 3. |
timeout |
Default auto-close time in milliseconds; 0 disables auto-close. |
add(value, options?) |
Adds an entry and returns its id. options accepts id and a per-entry timeout. |
close(id, overflowed?) |
Starts the close phase; overflowed marks a limit-driven close. |
remove(id) |
Removes a closed entry after the renderer’s exit transition. |
pause(reason?) / resume(reason?) |
Pauses or resumes timers by named, overlapping reasons. |
bindPresenceStackPause() |
Opt-in pointer/focus pause on an application-owned host. |
snapshot |
Contains records and paused; records contain id, value, open, overflowed, and remaining. |
The Web Component additionally provides configure(), options,
exitDuration, topLayer, pauseOn, resumeDelay, presencechange, the
host box, --floating-presence-count, --floating-presence-hit-span, and
declarative template bindings. Vue exposes reactive snapshot, records,
and paused through the composable, plus opt-in pauseTarget / pauseOn.