Skip to content

Modal

import { Modal } from '@delightstack/components';
View code
<script>
import { Modal, Button } from '@delightstack/components';
let open = $state(false);
</script>
<Button onclick={() => open = true}>Open Modal</Button>
<Modal bind:open title="Example Modal">
<p>This is a modal dialog with some example content.
Click outside or press Escape to close.</p>
</Modal>
View code
<Modal bind:open title="Edit Profile">
<p>Update your profile information below.</p>
{#snippet footer()}
<Button transparent onclick={() => open = false}>Cancel</Button>
<Button accent onclick={() => open = false}>Save Changes</Button>
{/snippet}
</Modal>

Replace the entire header with a custom snippet.

View code
<Modal bind:open>
{#snippet header()}
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
<h2 style="margin: 0; font-size: 1.25rem;">Settings</h2>
<span style="font-size: 0.85rem; opacity: 0.7;">Configure your preferences</span>
</div>
{/snippet}
<p>Settings content goes here.</p>
</Modal>

Add content before the title or after it (before the close button).

View code
<Modal bind:open title="Document">
{#snippet header_start()}
<span class="status-badge">Draft</span>
{/snippet}
{#snippet header_end()}
<Button transparent dense icon aria-label="Share">
<ShareIcon />
</Button>
{/snippet}
<p>Document content...</p>
</Modal>

Override the default sizing with explicit CSS values.

View code
<Modal bind:open title="Wide Modal" width="800px" height="500px">
<p>This modal has a custom size of 800x500px.</p>
</Modal>

Prevent closing via Escape key and backdrop click for required flows.

View code
<Modal bind:open title="Required Step" closable={false}>
<p>You must complete this step before continuing.</p>
{#snippet footer()}
<Button accent onclick={() => open = false}>Done</Button>
{/snippet}
</Modal>

Animate the modal from a specific trigger element for a connected spatial experience.

<script>
import { Modal, Button } from '@delightstack/components';
let showModal = $state(false);
let triggerEl = $state<HTMLElement>();
</script>
<Button bind:element={triggerEl} onclick={() => showModal = true}>
Open
</Button>
<Modal bind:open={showModal} title="Hero Transition" transition_target={triggerEl}>
<p>This modal animated from the button.</p>
</Modal>
PropTypeDefaultDescription
openbooleanfalseControls visibility ($bindable())
titlestring''Modal title text
closablebooleantrueAllow closing via Escape key and backdrop click
disable_close_iconbooleanfalseHide the X close button
modal_idstring''ID used for transition target tracking
widthstring''Explicit CSS width (desktop)
heightstring''Explicit CSS height (desktop)
max_widthstring'calc(100vw - 2rem)'Maximum width
max_heightstring'calc(100svh - 2rem)'Maximum height
transition_targetHTMLElement | ElementundefinedElement to animate from when opening
stylestring''Inline CSS styles
classstring''Additional CSS classes
childrenSnippetundefinedMain modal content
headerSnippetundefinedReplace entire header
header_startSnippetundefinedContent before the title
header_endSnippetundefinedContent after the title (before close button)
footerSnippetundefinedFooter content area
footer_startSnippetundefinedLeft side of footer
footer_endSnippetundefinedRight side of footer
onopen() => voidundefinedCalled when the modal opens
onclose() => boolean | undefined | voidundefinedCalled when the modal closes. Return false to prevent closing.
onbackdropclick() => voidundefinedCalled when the backdrop is clicked
EventDetailDescription
onopennoneFires when the modal mounts
onclosenoneFires when the modal is being closed. Return false to prevent it.
onbackdropclicknoneFires when the backdrop overlay is clicked
  • role="dialog" with aria-modal="true"
  • aria-labelledby pointing to the title element
  • aria-describedby pointing to the body content
  • Focus trap via focusTrap from @delightstack/utilities (Tab cycles within modal only)
  • Escape key dismissal (when closable is true)
  • Body scroll is locked while the modal is open (html { overflow: hidden })
  • Backdrop has a blur effect for visual depth