Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
The native HTML modal dialog element with built-in backdrop, focus trapping, and keyboard handling.
The <dialog> element provides a native way to create modal dialogs, confirmation prompts, and slide-in drawer panels. It handles complex accessibility requirements automatically: focus trapping, ESC to close, role="dialog", and inert background.
There are two ways to open a dialog:
command="show-modal" / command="close"showModal() / show() / close()In Vanilla Breeze, dialogs also serve as drawer panels via data-position, and are used internally by web components like <command-palette>, <short-cuts>, and <settings-panel>.
data-position)<details><tool-tip><toast-msg><drop-down> or <context-menu>The Invokers API lets you open and close dialogs with HTML attributes alone — no JavaScript required.
<!-- Trigger button --><button commandfor="my-dialog" command="show-modal">Open Dialog</button> <!-- Dialog --><dialog id="my-dialog"> <header> <h3>Dialog Title</h3> </header> <p>Content goes here.</p> <footer> <button commandfor="my-dialog" command="close">Close</button> </footer></dialog>
| Command | Description |
|---|---|
show-modal |
Opens dialog as modal (equivalent to showModal()) |
close |
Closes the dialog (equivalent to close()) |
request-close |
Requests closure (can be cancelled with cancel event) |
Invokers API is supported in Chrome 135+, Edge 135+, Safari TP, and Firefox Nightly. Include the polyfill for broader support:
<script type="module" src="https://unpkg.com/invokers-polyfill"></script>
| Feature | showModal() |
show() |
|---|---|---|
| Backdrop | Yes — via ::backdrop |
No |
| Focus trap | Yes — focus stays inside | No — focus can leave |
| ESC to close | Yes — native behavior | No |
| Inert background | Yes — page is non-interactive | No — page remains interactive |
| Top layer | Yes — above all content | Normal stacking context |
</section> <section> <h2>Variants</h2> <p>Basic dialog, confirmation prompt, and form dialog all use the same structure with different content.</p> <browser-window src="/docs/snippets/demos/dialog-variants.html" url="dialog-variants.html" title="Dialog variants" shadow></browser-window> </section> <section> <h2>Form method="dialog"</h2> <p>Forms inside dialogs can use <code>method="dialog"</code> to close the dialog on submit, passing the submit button's <code>value</code> as <code>dialog.returnValue</code>.</p> <browser-window src="/docs/snippets/demos/dialog-form-method.html" url="dialog-form-method.html" title="Form method dialog" shadow></browser-window> <code-block language="html" show-lines label="Form with method=dialog" data-escape><dialog id="choice-dialog"> <form method="dialog"> <header><h3>Choose</h3></header> <p>Select your preference:</p> <footer> <button type="submit" value="cancel">Cancel</button> <button type="submit" value="confirm">Confirm</button> </footer> </form></dialog> <script>const dialog = document.getElementById('choice-dialog');dialog.addEventListener('close', () => { console.log('User chose:', dialog.returnValue); // "cancel" or "confirm"});</script>
Use data-size to control dialog width:
| Attribute | Width | Use Case |
|---|---|---|
data-size="s" |
24rem (384px) | Simple confirmations, alerts |
| default | 32rem (512px) | Standard dialogs, forms |
data-size="l" |
48rem (768px) | Complex content, tables |
data-size="full" |
95vw / 95dvh | Full-screen experiences |
Use data-position to turn any dialog into a slide-in drawer panel. No additional JavaScript required — drawers inherit all dialog features: Invokers API, ESC to close, focus trapping, backdrop.
<!-- Right drawer --><button commandfor="nav-drawer" command="show-modal">Menu</button> <dialog id="nav-drawer" data-position="end"> <header><h3>Navigation</h3></header> <section> <nav>...</nav> </section> <footer> <button commandfor="nav-drawer" command="close">Close</button> </footer></dialog>
| Attribute | Position | Use Case |
|---|---|---|
data-position="end" |
Right (in LTR) | Navigation, settings panels |
data-position="start" |
Left (in LTR) | Sidebar, filters |
data-position="bottom" |
Bottom | Mobile action sheets, share menus |
data-position="top" |
Top | Banners, announcements |
Slide animations respect prefers-reduced-motion, falling back to a simple fade.
The bottom drawer variant (data-position="bottom") acts as a mobile-native bottom sheet — ideal for action menus, share sheets, and quick option lists. It automatically includes safe-area padding for devices with home indicators.
<button commandfor="photo-actions" command="show-modal"> Photo Options</button> <dialog id="photo-actions" data-position="bottom"> <header data-layout="cluster" data-layout-justify="between"> <h2>Photo Options</h2> <button commandfor="photo-actions" command="close" class="ghost icon-only" aria-label="Close"> <icon-wc name="x"></icon-wc> </button> </header> <section> <nav aria-label="Actions"> <ul> <li><a href="#"><icon-wc name="share"></icon-wc> Share</a></li> <li><a href="#"><icon-wc name="bookmark"></icon-wc> Save</a></li> <li><a href="#"><icon-wc name="copy"></icon-wc> Copy link</a></li> </ul> </nav> </section></dialog>
Add data-gesture="dismiss-down" to let users swipe the bottom sheet downward to close it. VB’s gesture library handles the drag tracking, opacity falloff, and snap-back automatically. The dialog closes with returnValue set to "dismiss".
<dialog id="my-sheet" data-position="bottom" data-gesture="dismiss-down"> <!-- drag handle indicator (CSS pseudo-element) --> <header data-layout="cluster" data-layout-justify="between"> <h2>Options</h2> <button commandfor="my-sheet" command="close" class="ghost icon-only" aria-label="Close"> <icon-wc name="x"></icon-wc> </button> </header> <section>...</section></dialog>
The gesture is lazy-loaded — it only ships when [data-gesture] attributes are present on the page. Buttons and links inside the sheet remain interactive; the drag only activates from non-interactive areas.
See also: Bottom Sheet on the Mobile page
For consistent styling, use <header>, content, and <footer>:
<dialog> <header> <h3>Dialog Title</h3> </header> <!-- Main content --> <p>Dialog content goes here.</p> <footer> <button type="button" class="secondary">Cancel</button> <button type="button">Confirm</button> </footer></dialog>
dialog { max-inline-size: min(90vw, 32rem); max-block-size: 85dvh; border: none; border-radius: var(--radius-l); background: var(--color-surface); color: var(--color-text); box-shadow: /* multi-layer elevation shadow */; overflow: hidden;} dialog > header { padding: var(--size-m) var(--size-l); border-block-end: 1px solid var(--color-border);} dialog > :is(p, section, form) { padding: var(--size-l);} dialog > footer { padding: var(--size-m) var(--size-l); border-block-start: 1px solid var(--color-border); display: flex; flex-wrap: wrap; gap: var(--size-s); justify-content: flex-end;}
The ::backdrop pseudo-element styles the overlay behind modal dialogs. VB defaults to a semi-transparent dark background with a subtle blur.
dialog::backdrop { background: oklch(0% 0 0 / 0.5); backdrop-filter: blur(2px);}
Dialogs include smooth entry animations using both @starting-style (for supporting browsers) and keyframe fallbacks. Drawer variants use directional slide-in animations. All animations respect prefers-reduced-motion.
/* Entry animation with @starting-style */dialog[open] { opacity: 1; transform: scale(1); transition: opacity var(--motion-enter-duration) var(--ease-out), transform var(--motion-enter-duration) var(--ease-out), display var(--motion-enter-duration) allow-discrete, overlay var(--motion-enter-duration) allow-discrete;} @starting-style { dialog[open] { opacity: 0; transform: scale(0.9); }} /* Fallback keyframe animation */dialog[open] { animation: vb-scale-in var(--motion-enter-duration) var(--ease-out);} @media (prefers-reduced-motion: reduce) { dialog[open] { animation: none; transition: none; }}
/* Drawers use directional slide-in animations */@keyframes vb-slide-in-end { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; }} @keyframes vb-slide-in-bottom { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; }} /* Reduced motion: fade instead of slide */@media (prefers-reduced-motion: reduce) { dialog[data-position][open] { animation: vb-fade-in var(--motion-enter-duration) var(--ease-out); }}
| Method | Description |
|---|---|
showModal() |
Opens dialog as modal with backdrop and focus trap |
show() |
Opens dialog as non-modal |
close(returnValue?) |
Closes dialog, optionally setting returnValue |
| Property | Type | Description |
|---|---|---|
open |
boolean | Whether the dialog is currently open |
returnValue |
string | Value set by close() or method="dialog" form submission |
| Event | Description |
|---|---|
close |
Fired when the dialog is closed |
cancel |
Fired when ESC is pressed (can be prevented with event.preventDefault()) |
const dialog = document.querySelector('dialog'); // Open as modaldialog.showModal(); // Listen for closedialog.addEventListener('close', () => { console.log('Closed with:', dialog.returnValue);}); // Prevent ESC from closingdialog.addEventListener('cancel', (event) => { event.preventDefault();});
Native dialogs do not close when clicking the backdrop. Add this small script if you want that behavior:
The <dialog> element provides these accessibility features automatically:
cancel event)role="dialog"<h3> in <header>) for screen reader context| Key | Action |
|---|---|
| Tab | Move focus between focusable elements (trapped inside modal) |
| Shift + Tab | Move focus backwards (trapped inside modal) |
| Escape | Close the modal dialog |
Dialogs are hidden entirely in print stylesheets — they are transient UI and should not appear on paper.
/* Print: dialogs are hidden entirely */@media print { dialog { display: none; }}
Several VB web components use dialog patterns internally:
| Component | How It Uses Dialog |
|---|---|
<command-palette> |
Keyboard-driven command modal with search filtering |
<short-cuts> |
Keyboard shortcuts reference modal |
<settings-panel> |
Settings popover with role="dialog" |
<details> — For inline expandable content (not overlays)<form> — Forms inside dialogs with method="dialog"<button> — Trigger buttons for opening dialogs (via Invokers API)<header> — Dialog title area<footer> — Dialog action button area<tool-tip> — For non-blocking hints and tips<toast-msg> — For transient notifications<drop-down> — For dropdown menusReady-to-use dialog patterns:
Basic, confirmation, form, and scrollable dialog patterns
Slide-in panels for navigation, settings, and filters
Mobile action menus, share sheets, and option lists
The <dialog> element is supported in all modern browsers.