kit

Dialog

A modal on the native dialog element, opened and closed from markup, with Escape, focus return and an inert page behind it for free.

Tier 2 · behaviour Native dialog beta

Example

Closed with: nothing yet

Dialog title

Supporting text for the dialog. Press Escape, click the backdrop or use a button to close it.

Delete this item?

This can't be undone.

Rename item

Show the markup
<div class="kit-cluster">
  <button class="kit-button" commandfor="demo-dialog" command="show-modal">Open dialog</button>
  <button class="kit-button" data-variant="danger" commandfor="demo-confirm" command="show-modal">Delete item</button>
  <button class="kit-button" commandfor="demo-form-dialog" command="show-modal">Dialog with a form</button>
</div>
<p class="demo-note" id="dialog-out">Closed with: nothing yet</p>

<dialog class="kit-dialog" id="demo-dialog" aria-labelledby="demo-dialog-title" closedby="any" data-demo-note="dialog-out">
  <header class="kit-dialog-header"><h2 id="demo-dialog-title">Dialog title</h2><button class="kit-button" data-variant="ghost" data-icon type="button" commandfor="demo-dialog" command="close" aria-label="Close"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true"><path d="m4 4 8 8m0-8-8 8"/></svg></button></header>
  <div class="kit-dialog-body"><p>Supporting text for the dialog. Press Escape, click the backdrop or use a button to close it.</p></div>
  <footer class="kit-dialog-footer"><button class="kit-button" type="button" commandfor="demo-dialog" command="close" value="cancel">Cancel</button><button class="kit-button" data-variant="primary" type="button" commandfor="demo-dialog" command="close" value="confirm">Confirm</button></footer>
</dialog>

<dialog class="kit-dialog" id="demo-confirm" data-size="sm" aria-labelledby="demo-confirm-title" aria-describedby="demo-confirm-text" data-demo-note="dialog-out">
  <header class="kit-dialog-header"><h2 id="demo-confirm-title">Delete this item?</h2></header>
  <div class="kit-dialog-body"><p id="demo-confirm-text">This can't be undone.</p></div>
  <footer class="kit-dialog-footer"><button class="kit-button" type="button" commandfor="demo-confirm" command="close" value="cancel" autofocus>Cancel</button><button class="kit-button" data-variant="danger" type="button" commandfor="demo-confirm" command="close" value="delete">Delete</button></footer>
</dialog>

<dialog class="kit-dialog" id="demo-form-dialog" aria-labelledby="demo-form-title" data-demo-note="dialog-out">
  <form method="dialog" class="kit-stack" data-gap="0">
    <header class="kit-dialog-header"><h2 id="demo-form-title">Rename item</h2></header>
    <div class="kit-dialog-body">
      <div class="kit-field"><label for="demo-form-name">Name</label><input class="kit-input" id="demo-form-name" name="name" value="Item one" required></div>
    </div>
    <footer class="kit-dialog-footer"><button class="kit-button" type="button" commandfor="demo-form-dialog" command="close" value="cancel">Cancel</button><button class="kit-button" data-variant="primary" value="save">Save</button></footer>
  </form>
</dialog>

Usage

<button class="kit-button" commandfor="confirm" command="show-modal">Delete item</button>

<dialog class="kit-dialog" id="confirm" aria-labelledby="confirm-title" closedby="any">
  <header class="kit-dialog-header">
    <h2 id="confirm-title">Delete this item?</h2>
    <button class="kit-button" data-variant="ghost" data-icon commandfor="confirm" command="close" aria-label="Close">…</button>
  </header>
  <div class="kit-dialog-body"><p>Supporting text.</p></div>
  <footer class="kit-dialog-footer">
    <button class="kit-button" commandfor="confirm" command="close">Cancel</button>
    <button class="kit-button" data-variant="danger" commandfor="confirm" command="close" value="delete">Delete</button>
  </footer>
</dialog>

commandfor and command are the platform's own invoker commands; dialog.js (in kit.js) fills them in for browsers that don't have them yet, so no script of yours is needed to open or close a dialog.

Markup Does
command="show-modal" opens the dialog as a modal
command="close" closes it; the button's value becomes dialog.returnValue
command="request-close" closes it unless a cancel listener says no
closedby="any" a click on the backdrop closes it too
data-size sm 24rem, md 32rem (default), lg 48rem

Listen for close on the dialog and read returnValue to learn which button closed it. A form inside with method="dialog" closes it on submit, after the browser's own validation.

Accessibility

  • showModal() makes everything behind the dialog inert and traps focus inside; Escape closes it. Focus goes to the first focusable element, or to one with autofocus: put autofocus on the safe choice (Cancel) for destructive dialogs.
  • When it closes, focus returns to the button that opened it.
  • Name it with aria-labelledby pointing at its heading; add aria-describedby for a short message the screen reader should read straight away.
  • Leave closedby="any" off dialogs holding unsaved input, so a stray click doesn't throw work away.
  • The page behind stops scrolling while a modal is open. Opening animates unless the person prefers reduced motion.