Dialog #

A Dialog is a modal that overlays the entire page. It uses the native dialog element, so opening it with showModal() puts children in the browser's top layer, escaping ancestor stacking (avoiding bugs and caveats, like unwanted overflow containment and cascading styles, without using Teleport). The native element also traps focus, makes the rest of the page inert, closes on Escape, and restores focus to the previously focused element on close.

Basic usage
#

Mounting the component opens the dialog, so the simplest usage guards it with {#if}. Wrap the content in an optional DialogContent for a padded, centered .pane card with a close button:

<button onclick={() => (opened = true)}> open a dialog </button> {#if opened} <Dialog onclose={() => (opened = false)}> <DialogContent> {#snippet children({close})} <h1>attention</h1> <p>this is a dialog</p> <button onclick={close}>ok</button> {/snippet} </DialogContent> </Dialog> {/if}

The {#if} guard isn't required. Pass show to let the component manage its own rendering (it defaults to true):

<Dialog show={opened} onclose={() => (opened = false)}> <DialogContent> {#snippet children({close})} <button onclick={close}>ok</button> {/snippet} </DialogContent> </Dialog>

DialogContent
#

DialogContent is a .pane card that shrinks to fit its content, up to max_width. Tune it with class and other forwarded attributes, adjust the surrounding space with gutter and the inner space with padding, and toggle the card style with pane={false} for an unstyled surface:

<Dialog show={opened} onclose={() => (opened = false)}> <!-- the defaults: --> <DialogContent class={undefined} padding="var(--space_xl)" gutter="var(--space_xl3)" max_width="var(--distance_md)" pane={true} close_button={true} > {#snippet children({close})} <button onclick={close}>ok</button> {/snippet} </DialogContent> </Dialog>

DialogContent adds a close button in the top-right corner, rendered after the content so it doesn't take initial focus. Pass close_button={false} to remove it, or a Snippet to render your own. The snippet receives attrs, typed DialogCloseButtonAttrs — the default button's placement, styling, a11y, and the onclick that closes the dialog — plus the DialogContext. Spread attrs to inherit the corner-anchored button and override only the glyph, or drop it to place the button freely:

<Dialog show={opened} onclose={() => (opened = false)}> <DialogContent> {#snippet close_button(attrs)} <button {...attrs} class="xs plain">close</button> {/snippet} <Code content="..." /> </DialogContent> </Dialog>

DialogContent is optional. Pass children directly for full control of the surface:

{#if opened} <Dialog onclose={() => (opened = false)}> freestyle </Dialog> {/if}

Alignment
#

By default the dialog is centered. Pass align="top" to anchor it near the top of the page and grow downward, which avoids the jumpiness of a centered dialog whose content changes height. Add and remove items in the demo to compare:

Overflow
#

Tall content scrolls within the dialog rather than the page behind it:

Dismissing
#

Clicking outside the content closes the dialog by default. Pass dismissable={false} to disable click-outside (Escape and your own buttons still close it):

Guarding close
#

To intercept closing, pass onbeforeclose and return false to keep the dialog open, which is useful for confirming unsaved changes. Closing programmatically via show={false} bypasses it:

<Dialog show={opened} onbeforeclose={() => !dirty || confirm('Discard unsaved changes?')} onclose={() => (opened = false)} > <DialogContent> {#snippet children({close})} <button onclick={close}>close</button> {/snippet} </DialogContent> </Dialog>

Nested dialogs
#

Dialogs can open more dialogs, each stacking in the top layer. The deepest level in this demo renders its own multi-pane surface via the bare-children path: