Tip of the week #018: Create modals and dialogs with the <dialog> element

A native way to create modals and dialogs, accessible and keyboard-friendly, using only HTML and a tiny bit of Javascript.

The <dialog> element is a nifty little thing. It's a relatively new HTML element, but it already has 96.66% browser support according to caniuse.com (at the time of writing), and its Baseline status is "Widely available":

It's a pain to create dialogs in the first place. At least if you want to make it keyboard accessible. With <dialog> all of this is built in.

This is what the code looks like:

Language: html
<dialog>
  <p>Come on!</p>
</dialog>

How to use it

You need to add a bit more than just the <dialog> to make it work. I suggest that you read the documentation for it (developer.mozilla.org), but here's a quick summary:

  • Add the <dialog> element and content to it (e.g. a <p>-tag)
  • Add a <button> inside. This will be our close button.
  • Add a <button> outside. This will be our open button.
  • Add a tiny bit of Javascript

Here's an example:

Language: html
<dialog id="myDialog">
  <button id="closeButton" autofocus>Close</button>
  <p>Come on!</p>
</dialog>
<button id="showButton">Show</button>

<script>
  const dialog = document.getElementById("myDialog");
  const showButton = document.getElementById("showButton");
  const closeButton = document.getElementById("closeButton");
  
  showButton.addEventListener("click", () => {
    dialog.showModal();
  });
  
  closeButton.addEventListener("click", () => {
    dialog.close();
  });
</script>

How to style it

You can style it just as you'd normally style other elements, but you should be aware of some things:

  • It comes with some default styling (see the code block below)
  • When the <dialog> is shown, it gets an attribute called open, which then styles the dialog from display: none to display: block. If you want to change the display value of your dialog (e.g. to flex or grid), be sure to use the attribute selector dialog[open] to do so, otherwise it will be visible at all times.
  • You can style the backdrop (the dark shadowy thing that's behind the dialog) using the ::backdrop psuedo selector
  • You don't have to worry about z-index. When opened, gets placed in the top-layer, safely above everything.

Default styling

The user agent stylesheet in Chrome looks like this:

Language: css
dialog:modal {
  overlay: auto !important;
}

dialog:-internal-dialog-in-top-layer {
  position: fixed;
  inset-block-start: 0px;
  inset-block-end: 0px;
  max-width: calc(100% - 2em - 6px);
  max-height: calc(100% - 2em - 6px);
  user-select: text;
  visibility: visible;
  overflow: auto;
}

dialog[open] {
  display: block;
}

dialog {
  display: none;
  position: absolute;
  inset-inline-start: 0px;
  inset-inline-end: 0px;
  width: fit-content;
  height: fit-content;
  background-color: canvas;
  color: canvastext;
  margin: auto;
  border-width: initial;
  border-style: solid;
  border-color: initial;
  border-image: initial;
  padding: 1em;
}

dialog:-internal-dialog-in-top-layer::backdrop {
  position: fixed;
  inset: 0px;
  background: rgba(0, 0, 0, 0.1);
}

Accessibility and keyboard navigation

The biggest benefits of using this element is that it's accessible and has keyboard navigation by default. You can press ESC on your keyboard to close it. Also, when using TAB all elements outside the <dialog> becomes inert, meaning that they're not going to get focus. This is a good thing, as it prevents the user to focus something beneath the dialog. Just be sure to add a close button within the dialog with clear labelling, so that users have a way to back out of the dialog.


Demo

Here's a Codepen demo of the element in use. Note the red backdrop styling.

See the Pen Untitled by Håvard Brynjulfsen (@havardob) on CodePen.