Layers
Popovers, menus, sheets and dialogs close the same way: Escape, a press outside, or focus leaving, always the topmost layer first.
Dismissable layers
dismissable() registers an element as a layer and calls onDismiss with the
reason when the user dismisses it. The component decides what to do, usually close.
Open Filters, then More filters.
<script>
import { dismissable } from 'sveltier';
let open = $state(false);
let trigger = $state();
</script>
<button bind:this={trigger} aria-expanded={open} onclick={() => (open = !open)}>Filters</button>
{#if open}
<!-- The trigger counts as inside, so pressing it toggles instead of dismissing. -->
<div {@attach dismissable({ onDismiss: () => (open = false), inside: [trigger] })}>
…
</div>
{/if}| Name | Type | Default | Description |
|---|---|---|---|
onDismiss | (reason: 'escape' | 'outside' | 'focus') => void | – | Called when the user dismisses the layer. The component decides whether to close. |
escape | boolean | true | Dismiss on Escape, unless an element inside handled it (event.preventDefault()). |
outside | boolean | true | Dismiss on a press outside: on pointerdown for mouse and pen, on the click that ends a tap for touch. |
focusOutside | boolean | false | Dismiss when focus moves outside, as menus and non-modal popovers do on Tab. |
inside | Element[] | (() => Element[]) | [] | Elements that count as inside, such as the trigger. |
One stack
Every layer joins one stack, and only the topmost one reacts. A press outside a nested layer
closes that layer alone; the next press closes the one below. An element inside that handles
Escape itself, such as a search field clearing its text, calls event.preventDefault() and the layer stays open.
Sveltier's floating components put their layer in the top layer with popover="manual" and leave dismissal to this stack, so a popover inside a sheet inside
a dialog behaves the same at every level.
Touch
A mouse or pen dismisses on press. A touch waits for the tap to end, so scrolling the page with a finger never closes a layer.