Dialog
Modal dialogs with focus trap and backdrop handling.
Props
Features
Section titled “Features”- 🪟 Uses native HTML
<dialog>element - 🔒 Built-in scroll lock when open
- 🎯 Focus trap and focus management
- 🎨 Animation support with custom overlay
<script lang="ts"> import { Dialog } from "melt/builders";
const dialog = new Dialog();</script>
<button {...dialog.trigger}> Open Dialog</button>
<!-- Optional overlay for animations --><div {...dialog.overlay}></div>
<dialog {...dialog.content}> Dialog content</dialog>Animations
Section titled “Animations”Dialogs support smooth animations for both the content and backdrop. To enable animations, use the overlay element instead of the native ::backdrop pseudo-element.
Basic Animation Setup
Section titled “Basic Animation Setup”<script lang="ts"> import { Dialog } from "melt/builders";
const dialog = new Dialog();</script>
<button {...dialog.trigger}> Open Dialog</button>
<!-- Overlay with fade animation --><div {...dialog.overlay}></div>
<!-- Dialog with scale and fade animation --><dialog {...dialog.content}> <h2>Hello!</h2> <button onclick={() => dialog.open = false}>Close</button></dialog>
<style> dialog { width: 24rem; background: white; opacity: 0; scale: 0.95; transition: ease 300ms; }
dialog::backdrop { display: none; }
dialog[data-open] { opacity: 1; scale: 1; }
[data-melt-dialog-overlay] { position: fixed; width: 100%; height: 100%; background: black; opacity: 0; transition: ease 300ms; }
[data-melt-dialog-overlay][data-open] { opacity: 0.1; }</style>How It Works
Section titled “How It Works”When you set dialog.open = true:
- The overlay element shows using the Popover API
- The dialog element shows with
showModal() - A tick() occurs to update the
data-openattribute - CSS transitions animate the elements to their open state
When you set dialog.open = false:
- The
data-openattribute is removed immediately - CSS transitions animate elements to their closed state
- When transitions complete, the dialog and overlay are closed
Animation Tips
Section titled “Animation Tips”- Transition Duration: Ensure the
transitionendevent fires by using CSS transitions with non-zero duration - Backdrop: Hide the native backdrop with
dialog::backdrop { display: none; }when using the custom overlay
API Reference
Section titled “API Reference”Constructor Props
The props that are passed when calling
new Dialog()new Dialog()export type DialogProps = { /** * If the Dialog is open. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * @default false */ open?: MaybeGetter<boolean | undefined>;
/** * Called when the value is supposed to change. */ onOpenChange?: (value: boolean) => void;
/** * If the dialog should close when clicking escape. * * @default true */ closeOnEscape?: MaybeGetter<boolean | undefined>;
/** * If the dialog should close when clicking outside. * Alternatively, accepts a function that receives the clicked element, * and returns if the dialog should close. * * @default true */ closeOnOutsideClick?: MaybeGetter<boolean | undefined>;
/** * If the dialog should lock the document scroll when open. * * @default true */ scrollLock?: MaybeGetter<boolean | undefined>;};export type DialogProps = { /** * If the Dialog is open. * * When passing a getter, it will be used as source of truth, * meaning that the value only changes when the getter returns a new value. * * Otherwise, if passing a static value, it'll serve as the default value. * * @default false */ open?: MaybeGetter<boolean | undefined>;
/** * Called when the value is supposed to change. */ onOpenChange?: (value: boolean) => void;
/** * If the dialog should close when clicking escape. * * @default true */ closeOnEscape?: MaybeGetter<boolean | undefined>;
/** * If the dialog should close when clicking outside. * Alternatively, accepts a function that receives the clicked element, * and returns if the dialog should close. * * @default true */ closeOnOutsideClick?: MaybeGetter<boolean | undefined>;
/** * If the dialog should lock the document scroll when open. * * @default true */ scrollLock?: MaybeGetter<boolean | undefined>;};Properties
The properties returned from
new Dialog()new Dialog()-
closeOnEscape
booleanboolean -
closeOnOutsideClick
booleanboolean -
scrollLock
booleanboolean -
refs
{get: (key: "trigger" | "content" | "overlay") => HTMLElement | undefinedattach: (key: "trigger" | "content" | "overlay") => Attachment<HTMLElement>key: any}{get: (key: "trigger" | "content" | "overlay") => HTMLElement | undefinedattach: (key: "trigger" | "content" | "overlay") => Attachment<HTMLElement>key: any} -
open
booleanboolean -
sharedProps
{ "data-open": "" | undefined }{ "data-open": "" | undefined } -
trigger
{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-trigger": ""readonly onclick: () => voidreadonly type: "button"}{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-trigger": ""readonly onclick: () => voidreadonly type: "button"}The trigger element. -
content
{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-content": ""readonly onclose: () => void}{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-content": ""readonly onclose: () => void}The element for the dialog itself. -
overlay
{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-overlay": ""readonly popover: "manual"readonly "aria-hidden": true}{readonly "data-open": "" | undefinedreadonly "data-melt-dialog-overlay": ""readonly popover: "manual"readonly "aria-hidden": true}Optional overlay element, to replace dialog::backdrop for animation support