dialog
The native HTML modal dialog element with built-in backdrop, focus trapping, and keyboard handling.
Description
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:
- HTML Invokers API (recommended) —
command="show-modal"/command="close" - JavaScript —
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>.
When to Use
- Confirmation dialogs: Confirm destructive actions before proceeding
- Form dialogs: Collect user input in an overlay
- Alert messages: Important information requiring acknowledgment
- Media lightboxes: Images or videos in a focused overlay
- Drawer panels: Slide-in navigation, settings, or filters (via
data-position)
When Not to Use
- For inline expandable content — use
<details> - For tooltips or popovers — use
<tool-tip> - For toast notifications — use
<toast-msg> - For dropdown menus — use
<drop-down>or<context-menu> - For non-blocking information — avoid interrupting user flow
Invokers API (Declarative)
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>
Available Commands
| 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) |
Browser Support
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>
showModal() vs show()
| 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>
Size Variants
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 |
Drawer Variants
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.
Bottom Sheet
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>
Drag to Dismiss
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
Dialog Structure
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>
Structural CSS
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;}
Backdrop Styling
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);}
Animation
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; }}
Drawer Animations
/* 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); }}
JavaScript API
Methods
| 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 |
Properties
| Property | Type | Description |
|---|---|---|
open |
boolean | Whether the dialog is currently open |
returnValue |
string | Value set by close() or method="dialog" form submission |
Events
| 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();});
Backdrop Click to Close
Native dialogs do not close when clicking the backdrop. Add this small script if you want that behavior:
Accessibility
Native Features
The <dialog> element provides these accessibility features automatically:
- Focus trapping: Tab key cycles through focusable elements inside the dialog
- Focus restoration: Focus returns to the triggering element when closed
- Inert background: Background content is non-interactive when modal is open
- ESC to close: Standard keyboard dismissal (can be prevented via
cancelevent) - ARIA semantics: Automatically has
role="dialog"
Best Practices
- Include a visible close button
- Use a heading in the dialog (
<h3>in<header>) for screen reader context - Keep dialogs focused — don't include unrelated content
- Provide keyboard alternatives for all actions
- Avoid nesting dialogs inside dialogs
Keyboard Support
| Key | Action |
|---|---|
| Tab | Move focus between focusable elements (trapped inside modal) |
| Shift + Tab | Move focus backwards (trapped inside modal) |
| Escape | Close the modal dialog |
Print Behavior
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; }}
Components Using Dialog
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" |
Related
<details>— For inline expandable content (not overlays)<form>— Forms inside dialogs withmethod="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 menus
Snippets
Ready-to-use dialog patterns:
Modal Dialog
Basic, confirmation, form, and scrollable dialog patterns
Drawer Dialog
Slide-in panels for navigation, settings, and filters
Bottom Sheet
Mobile action menus, share sheets, and option lists
Browser Support
The <dialog> element is supported in all modern browsers.
- Invokers API: Chrome 135+, Edge 135+, Safari TP, Firefox Nightly. Use the invokers-polyfill for broader support.
- @starting-style: Chrome 117+, Safari 17.5+. Other browsers fall back to keyframe animations.
- backdrop-filter: All modern browsers. The blur effect on the backdrop degrades gracefully.