Tips

The command attribute lets you open <dialog>s declaratively—no JavaScript required. Use command="show-modal" for modal dialogs or command="show" for non-modal ones.

<!-- Modal dialog (with backdrop, traps focus) -->
<button type="button" commandfor="modal-dialog" command="show-modal">
Open Modal
</button>
 
<!-- Non-modal dialog (no backdrop, doesn't trap focus) -->
<button type="button" commandfor="modeless-dialog" command="show">
Open Dialog
</button>
 
<dialog id="modal-dialog">
<p>I'm a modal dialog with a backdrop.</p>
<button type="button" commandfor="modal-dialog" command="close">Close</button>
</dialog>
 
<dialog id="modeless-dialog">
<p>I'm a non-modal dialog. You can still interact with the page.</p>
<button type="button" commandfor="modeless-dialog" command="close">Close</button>
</dialog>

The commandfor attribute points to the dialog’s ID. When clicked, show-modal calls showModal() and show calls show().

Modal dialogs:

  • Display a ::backdrop pseudo-element
  • Trap focus inside the dialog
  • Close on Escape key
  • Block interaction with the rest of the page

Non-modal dialogs:

  • No backdrop
  • Don’t trap focus
  • Don’t close on Escape by default
  • Allow interaction with the rest of the page

Demo:

Two buttons opening modal and non-modal dialogs respectively

The closedby attribute gives you declarative control over how users can dismiss a <dialog>. Want users to close by clicking outside? Set closedby="any". Need to force them through your buttons? Use closedby="none".

<!-- Light dismiss: Esc key OR clicking outside -->
<dialog id="light-dismiss-dialog" closedby="any">
<p>Click outside or press Esc to close me.</p>
<button type="button" commandfor="light-dismiss-dialog" command="close">
Or close manually
</button>
</dialog>
 
<!-- Platform dismiss only: Esc key, no clicking outside -->
<dialog id="platform-dismiss-dialog" closedby="closerequest">
<p>Press Esc to close, but clicking outside won't work.</p>
<button type="button" commandfor="platform-dismiss-dialog" command="close">
Close
</button>
</dialog>
 
<!-- Developer dismiss only: buttons/forms only -->
<dialog id="strict-dialog" closedby="none">
<p>You must use the button to close this dialog.</p>
<button type="button" commandfor="strict-dialog" command="close">
Close
</button>
</dialog>

The three values:

  • any — Light dismiss (clicking outside), platform actions (Esc key), and developer mechanisms (buttons)
  • closerequest — Platform actions and developer mechanisms only (no clicking outside)
  • none — Developer mechanisms only (buttons must be used)

By default, modal dialogs behave as closerequest (Esc closes them) and non-modal dialogs behave as none. The closedby attribute lets you override these defaults—finally giving us the “click outside to close” pattern without JavaScript.

Demo:

Two buttons opening modal and non-modal dialogs respectively

Continuing with closing <dialog>s, in addition to formmethod="dialog" you can also implement a dialog close button in the header with an invoker command="close". Perfect way to close a dialog without a <form>.

<dialog id="example-dialog" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
<header>
<hgroup>
<h2 id="dialog-title">Refund payment</h2>
<p id="dialog-desc">
Are you sure you want to refund this payment?
The funds will be returned to the customer's account.
</p>
</hgroup>
<button
type="button"
commandfor="example-dialog"
command="close"
aria-label="Close dialog"
>&times;</button>
</header>
</dialog>

The commandfor attribute points to the dialog’s ID, and command="close" tells the browser to close it when clicked—no JavaScript required.

Demo:

A dialog with a close button in the header that closes the dialog when clicked

<dialog>s with forms have a simple HTML-only way to implement “Cancel” buttons. In addition to POST and GET, <form>s inside of <dialog>s can make use of the dialog method. This allows the submit button to close the dialog without submitting the form to the backend.

<dialog id="example-dialog">
<form method="POST" action="/confirmations">
<footer>
<button type="submit" formmethod="dialog" formnovalidate value="cancel">
Cancel
</button>
<button type="submit">
Confirm
</button>
</footer>
</form>
</dialog>

The formmethod="dialog" attribute on the cancel button overrides the form’s method, closing the dialog instead of submitting. The formnovalidate attribute ensures validation doesn’t block the cancel action.

Demo:

A confirmation dialog with Cancel and Confirm buttons, where Cancel closes the dialog without submitting

Coming in the next version of Safari, and already in Chrome and Edge, you can now create <textarea>s that auto-grow with the field-sizing: content rule.

textarea {
field-sizing: content;
min-block-size: attr(rows rlh);
}

The min-block-size: attr(rows rlh) ensures the textarea still respects its rows attribute as a minimum height, using the rlh unit (root line height).

Demo:

A textarea that automatically grows as the user types more content