Modal Dialog

Native dialog patterns with forms, confirmation, and scrolling. Accessible by default with focus management.

Overview

Modal dialogs use the native HTML <dialog> element which provides built-in accessibility features: focus trapping, escape key handling, and proper ARIA semantics. No JavaScript library required.

Key features:

  • Native focus management and trapping
  • Escape key closes dialog
  • Built-in backdrop with ::backdrop
  • Form integration with method="dialog"
  • Return value for form submissions

Basic Modal

A simple modal with header, body content, and action buttons.

<button type="button" id="open-modal"> Open Modal </button> <dialog id="basic-modal"> <layout-stack data-layout-gap="l"> <header> <h2>Modal Title</h2> </header> <div class="dialog-body"> <p>This is a basic modal dialog.</p> </div> <footer> <layout-cluster data-layout-justify="flex-end" data-layout-gap="m"> <button type="button" class="secondary" id="cancel-btn"> Cancel </button> <button type="button" class="primary" id="confirm-btn"> Confirm </button> </layout-cluster> </footer> </layout-stack> </dialog> <script> const modal = document.getElementById('basic-modal'); document.getElementById('open-modal').onclick = () => modal.showModal(); document.getElementById('cancel-btn').onclick = () => modal.close(); document.getElementById('confirm-btn').onclick = () => modal.close('confirm'); </script>

Confirmation Dialog

A centered confirmation dialog for destructive actions. Uses method="dialog" for form submission.

<dialog id="confirm-modal" class="confirm-dialog"> <form method="dialog"> <layout-stack data-layout-gap="l"> <header> <icon-wc name="alert-triangle" size="lg" class="warning-icon"></icon-wc> <h2>Confirm Deletion</h2> </header> <p>Are you sure? This action cannot be undone.</p> <footer> <layout-cluster data-layout-justify="center" data-layout-gap="m"> <button type="submit" value="cancel" class="secondary">Cancel</button> <button type="submit" value="delete" class="danger">Delete</button> </layout-cluster> </footer> </layout-stack> </form> </dialog> <script> const modal = document.getElementById('confirm-modal'); modal.addEventListener('close', () => { if (modal.returnValue === 'delete') { // Perform delete action } }); </script>

Form Modal

A modal containing a form with inputs. Includes a close button in the header.

<dialog id="form-modal"> <form method="dialog"> <layout-stack data-layout-gap="l"> <header> <h2>Add New Item</h2> <button type="button" class="ghost close-btn" aria-label="Close"> <icon-wc name="x"></icon-wc> </button> </header> <div class="dialog-body"> <layout-stack data-layout-gap="m"> <form-field> <label for="item-name">Name</label> <input type="text" id="item-name" name="name" required> </form-field> <form-field> <label for="item-desc">Description</label> <textarea id="item-desc" name="description" rows="3"></textarea> </form-field> </layout-stack> </div> <footer> <layout-cluster data-layout-justify="flex-end" data-layout-gap="m"> <button type="button" class="secondary">Cancel</button> <button type="submit" value="save" class="primary">Save</button> </layout-cluster> </footer> </layout-stack> </form> </dialog>

Dialog Styles

Base styles for the dialog element. Add to your project CSS.

dialog { padding: 0; border: none; border-radius: var(--radius-l); box-shadow: var(--shadow-lg); max-width: min(90vw, 500px); max-height: 85vh; } dialog::backdrop { background: var(--color-overlay-strong); backdrop-filter: blur(4px); } dialog > layout-stack, dialog > form { padding: var(--size-l); } dialog header { display: flex; justify-content: space-between; align-items: center; gap: var(--size-m); } dialog header h2 { margin: 0; font-size: var(--font-size-xl); }

Usage Notes

  • Opening: Use dialog.showModal() for modal behavior with backdrop
  • Closing: Use dialog.close() or let users press Escape
  • Return value: Use method="dialog" on forms to get the submit button's value
  • Focus: First focusable element receives focus when opened
  • Click outside: Add click listener on dialog to close when clicking backdrop
  • Scrolling: For long content, add a scrollable class to the body section

Click Outside to Close

dialog.addEventListener('click', (e) => { if (e.target === dialog) { dialog.close(); } });

Related

Dialog Element

Native dialog documentation

Toast Notifications

Non-blocking notifications