all

The Dialog Tag

So far this year, I have been exploring many of the lesser-known HTML5 tags that are becoming more prevalent and also more useful as more information about them is released. Recently, the Chromium team has released an advisory suggesting that our good friends alert(), confirm(), and prompt() should no longer be used as they are app-modal, synchronous, and therefore they are not ideal to either the browser, developer, or user experience. Instead, the push is for developers to use the Web Notifications API for notifying users (a la alert()) and the <dialog> tag for retrieving feedback from the user (a la confirm() and prompt()). Since many of you are probably asking yourselves WTF the <dialog> tag is, I’m going to spend some time exploring it.

Background

First, a litle background on the <dialog> tag. At the time of this writing, global support for <dialog> has just crossed 56%, which means that most of the modern browsers are still not supporting it fully. That being said, it is part of the spec for HTML5, and support for it will continue to grow.

However, the idea behind the dialog tag is to provide a temporal, transient dialog with which to inform the user of some event or occurrence, and thereby replace what would normally be app-modal or even page-modal level application dialogs. Put another way (which usually means I didn’t put it well the first time), dialogs are style-able transient DOM elements which are designed for notification purposes, within the context of a web document. This is separate from the Notification API, which makes external notifications about events within a web application.

Basic Functionality

So, a <dialog> element is just another DOM element, however it is not shown by default. The default CSS for the element renders it as follows:

dialog {
  position: absolute;
  left: 0px;
  right: 0px;
  width: -webkit-fit-content;
  height: -webkit-fit-content;
  color: black;
  margin: auto;
  border-width: initial;
  border-style: solid;
  border-color: initial;
  border-image: initial;
  padding: 1em;
  background: white;
}

dialog:not([open]) {
  display: none;
}

There are specific DOM methods attached to a <dialog> element which allow you to trigger the browser’s default behaviour. These methods are .showModal() and .close(), which as you can expect will show the dialog as a page modal or close it, accordingly. If you want to force a dialog to be open on page load, add the open attribute to the opening <dialog> tag, and it will be displayed by default.

In order to be able to trigger these methods, you will need to get a reference to the dialog first. Given the following code:

<dialog id="dlg">
	<p>I'm a pretty dialog box!</p>
</dialog>

I would be able to fetch a reference to it with either of the following two lines of code:

let dialog = document.getElementById("dlg");
// OR
let dialog = document.querySelector("#dlg");

I will leave it to the fanboys and SJSW’s of the internet to debate over the relative merits of which approach to take to get a reference to the element. Once you have the reference, then you can simply call the .showModal() and .close() methods on them as you need:

dialog.showModal();
dialog.close();

Running the .showModal() method on the <dialog> will add the open attribute to the element automatically.

DO NOT MANUALLY REMOVE THE `OPEN` ATTRIBUTE.

Please, save yourself the heartache, and just don’t do it. If you manually remove the open attribute, the dialog does go away, but you have now broken the state of the element and can no longer run .showModal() or .close() on it, and it will no longer receive any close events, ever again. (This is important later)

Forms and Dialogs

As <dialog> elements are just normal block elements, you can put anything inside of them you want. Content, images, and yes, even forms. However, there are a few quirks I have found with forms in dialogs that you probably want to be aware of.

To begin with, there is a new method attribute value that a <form> tag will accept, when being used with a <dialog>. That value is just dialog, but it serves a dual purpose. First, when you have a form with method="dialog" set on it, submitting the form both triggers the submit event on the form but also closes the dialog.

Second, even if you have an action attribute defined on the form representing the endpoint to which the form should submit, it will not trigger a network event. Once you have method="dialog" on the <form> tag, you are wholly responsible for submitting the value(s) of that form programmatically. Fortunately, you do get the submit event being triggered on the form which you can add an event listener to, in order to process the form manually. I suspect this is a case where you would be submitting the form data via sockets, fetch, or AJAX.

One interesting note about forms and dialogs is that when you submit a form, whatever the value of the form’s submit control is will be set on the dialog object as the property returnValue. Consider the following code:

<dialog id="ageDlg">
	<form method="dialog">
		<label for="age">How old are you?</label>
		<input type="number" id="age" name="age" placeholder="Enter age">
		<input type="submit" value="I am old">
	</form>
</dialog>

When the above dialog is opened, a user will (likely, but not definitely) enter a number into the box, and click the button that says ‘I am old’. Once the form submit has occurred, and the dialog has closed, then the following code behaves as described:

let dlg = document.querySelector("#ageDlg");
console.log(dlg.returnValue); //'I am old'

Notably, the returnValue property has nothing to do with the form inputs themselves, and everything to do only with the value on the submit button.

Dialog events

There are only two events which you can bind to that are unique to <dialog> elements. One is the cancel event, which occurs when the Esc key is pressed, or the user-agent cancels the dialog.

The second event is the close event, which is an event shared by several objects in the browser world (WebSockets, for instance), but the close event for a dialog is fired when the dialog is closed, whether by the dialog being cancelled (see above), or by the .close() method being invoked on the dialog object itself. Remember earlier, when I talked about manually removing the open attribute? Here’s where it matters. If you manually remove the open attribute, you lose the ability to have a callback get triggered on the close event.

There is no event that is fired when a dialog is opened (through .showModal()), though I think there should be. I have experimented with things like onopen and onshow and neither were fired. Checking the spec confirmed my suspicion that nothing in .showModal() queues a task to fire an event.

Backdrops

The last aspect of the <dialog> element which I want to share is the pseudo-element ::backdrop which is created when the dialog is triggered. As the dialog is a modal element, there is an overlay that is automatically built by the browser as a pseudo-element to dim out content that is inaccessible while the dialog is open.

This pseudo-element can be referenced in CSS using the ::backdrop pseudo-class and starts out styled thusly:

dialog::backdrop {
  position: fixed;
  top: 0px;
  right: 0px;
  bottom: 0px;
  left: 0px;
  background: rgba(0, 0, 0, 0.0980392);
}

Of course, you are able to write your own styles which will give this whatever hue or appearance you want. There are probably a lot of usability lessons which can be applied here regarding what is the “right” level of alpha to use for dimming the background content.

Summary

So far, <dialog> only works reliably in Chrome at the time of this writing, and you can flip a flag in Firefox to enable it if you wanted to play with it there and compare user-agent behaviour. Whether we choose to adhere to the Chromium team’s admonition that alert() should go away, at least now we know what the heck the <dialog> element is.