Native Modals on the Web Using the HTML Dialog Element

The HTML 5.2 spec introduced a dialog element that makes it easy to implement native modals and pop-ups. Modals are an important part of almost any non-trivial web app, so it’s promising to think that there’s finally a way to implement one without re-inventing the wheel each time. You can click the button below for an example modal created using the dialog element:

 

In this post we’ll go over how to implement that modal.

Markup

Our markup is really simple and the only thing that’s new is the use of the dialog element:

<dialog class="my-modal">
  <h3>Well, hello there cowboy! 👋</h3>
  <p>I'm a fancy modal.</p>
  <div class="actions">
    <button class="small ok-modal-btn">Ok!</button>
    <button class="small close-modal-btn">Close</button>
  </div>
</dialog>

Note that we can also add an open attribute to the dialog element so that the modal starts-off opened:

<dialog class="my-modal" open>
  <!-- ... -->
</dialog>

Styles

The styling is also pretty straightforward. By default the dialog will have a black border, no visible backdrop and a width that’s matches the widest content. We can easily change that to make things a little prettier:

dialog {
  background-color: var(--bg);
  color: var(--text-color);
  text-align: center;
  border: none;
  padding: 2rem;
  border-radius: 6px;
  box-shadow: 0 0 40px rgba(0,0,0,0.1), 0 0 10px rgba(0,0,0,0.25);
  max-width: 90vw;
}

dialog[open] {
  animation: appear .15s cubic-bezier(0, 1.8, 1, 1.8);
}

dialog::backdrop {
  background: linear-gradient(45deg, rgba(0,143,104,.5), rgba(250,224,66,.5));
}

dialog .actions {
  display: flex;
  justify-content: space-around;
}

@keyframes appear {
  from {
    opacity: 0;
    transform: translateX(-3rem);
  }

  to {
    opacity: 1;
    transform: translateX(0);
  }
}

There are three interesting things to notice here:

  • The ::backdrop pseudo-selector allows to style the backdrop for the dialog.
  • We target the dialog’s open attribute to trigger an animation where our modal will slide-in from the left.
  • The color and background-color properties for the dialog default to black and white, respectively, instead of inheriting from the body element. Here we want the colors to match the site’s –text-color and –bg CSS variables so that the colors work with the site’s dark theme.

Opening and Closing the Modal

We have the markup and styles in place, but our modal is not functional just yet. We need to add a touch of JavaScript to open it when a user action like a button click happens.

To open it, it’s is as easy as calling the showModal method on the modal instance, and the close method for closing it:

const trigger = document.querySelector('.open-modal-btn');
const closeBtn = document.querySelector('.close-modal-btn');
const modal = document.querySelector('.my-modal');

trigger.addEventListener('click', () => {
  modal.showModal();
});
closeBtn.addEventListener('click', () => {
  modal.close();
});

When calling the close method, a string can be passed-in as an argument and the value will be available on the dialog’s returnValue attribute:

// ...
const modal = document.querySelector('.my-modal');
const okBtn = document.querySelector('.ok-modal-btn');

okBtn.addEventListener('click', () => {
  modal.close('Ok! 🤠');
});

Events

Dialog instances can listen to the close and cancel events. The cancel event is triggered when the dialog is closed by the user hitting the escape key. In our example, we listen for the close event to reflect the modal’s returnValue in a span’s innerText:

// ...
const modal = document.querySelector('.my-modal');
const okBtn = document.querySelector('.ok-modal-btn');
const message = document.querySelector('.message');

okBtn.addEventListener('click', () => {
  modal.close('Ok! 🤠');
});

modal.addEventListener('close', () => {
  message.innerText = modal.returnValue;
  modal.returnValue = '';
});

Note how we reset the value of returnValue to an empty string. This is because if we trigger our modal again the return value won't reset automatically.

Browser Support & Polyfill

At the time of this writing, only Chrome fully supports the dialog element and Firefox has support behind a flag. We can still use the element right away though, thanks to a polyfill that’s maintained by the Chrome team. The native implementation will be used in supporting browsers and the polyfill will add support for non-supporting browser.

You can add the script file for the polyfill directly into your pages, or you can add it to your project using npm or Yarn:

$ npm i dialog-polyfill

# or, using Yarn:
$ yarn add dialog-polyfill

You’ll also need to include a small stylesheet, which also comes bundled with the polyfill package, into your pages:

<head>
  <!-- ... -->
  <link rel="stylesheet" type="text/css" href="/path/to/dialog-polyfill.css" />
</head>

Once the polyfill is loaded, each dialog needs to be registered with the polyfill using the dialogPolyfill.registerDialog method:

const modal = document.querySelector('.my-modal');

dialogPolyfill.registerDialog(modal);

The other caveat is that the ::backdrop pseudo-selector won’t work even with the polyfill. To remedy that, the polyfill ads an element with a class of backdrop right after the dialog element. We can therefore style it to look just like our native backdrop:

dialog::backdrop {
  background: linear-gradient(45deg, rgba(0,143,104,.5), rgba(250,224,66,.5));
}
dialog + .backdrop {
  background: linear-gradient(45deg, rgba(0,143,104,.5), rgba(250,224,66,.5));
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

Browser Support

Can I Use dialog? Data on support for the dialog feature across the major browsers from caniuse.com.

Well, hello there cowboy! 👋

I'm a fancy modal.

  Tweet It

🕵 Search Results

🔎 Searching...