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 with type DialogCloseButtonAttrs with 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 ignore 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: