Skip to content
Sveltier
Mode
Primitives

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.

Filters.svelte
<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}
NameTypeDefaultDescription
onDismiss(reason: 'escape' | 'outside' | 'focus') => void–Called when the user dismisses the layer. The component decides whether to close.
escapebooleantrueDismiss on Escape, unless an element inside handled it (event.preventDefault()).
outsidebooleantrueDismiss on a press outside: on pointerdown for mouse and pen, on the click that ends a tap for touch.
focusOutsidebooleanfalseDismiss when focus moves outside, as menus and non-modal popovers do on Tab.
insideElement[] | (() => 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.