The HTML Dialog Element: Enhancing Accessibility and Ease of Use
Dialogs are a common component added to applications, whether on the web or in native applications. Traditionally there has not been a standard way of implementing these on the web, resulting in many ad-hoc implementations that don’t act consistently across different web applications. Often, commonly expected features are missing from dialogs due to the complexity of implementing them.
However, web browsers now offer a standard dialog element.
Why use the dialog element?
The native dialog element streamlines the implementation of dialogs, modals, and other kinds of non-modal dialogs. It does this by implementing many of the features needed by dialogs for you that are already baked into the browser.
This is helpful as it reduces the burden on the developer when making their applications accessible by ensuring that user expectations concerning interaction are met, and it can also potentially simplify the implementation of dialogs in general.
Basic usage
Adding a dialog using the new <dialog>
tag can be achieved with just a few lines of code.
<dialog id="example-dialog">
<button autofocus>Close</button>
<p>This is a modal with some text!</p>
</dialog>
However, adding the dialog alone won’t do anything to the page. It will show up only once you call the .showModal()
method against it.
document.getElementById('example-dialog').showModal();
Then if you want to close it you can call the .close()
method on the dialog, or press the escape key to close it, just like most other modals work. Also, note how a backdrop appears that darkens the rest of the page and prevents you from interacting with it. Neat!
Accessibility and focus management
Correctly handling focus is important when making your web applications accessible to all users. Typically you have to move the current focus to the active dialog when showing them, but with the dialog element that’s done for you.
By default, the focus will be set on the first focusable element in the dialog. You can optionally change which element receives focus first by setting the autofocus
attribute on the element you want the focus to start on, as seen in the previous example where that attribute was added to the close <button>
element.
Using the .showModal()
method to open the dialog also implicitly adds the dialog ARIA role to the dialog element. This helps screen readers understand that a modal has appeared and the screen so it can act accordingly.
Adding forms to dialogs
Forms can also be added to dialogs, and there’s even a special method
value for them. If you add a <form>
element with the method set to dialog
then the form will have some different behaviors that differ from the standard get
and post
form methods.
First off, no external HTTP request will be made with this new method. What will happen instead is that when the form gets submitted, the returnValue
property on the form element will be set to the value
of the submit button in the form.
So given this example form:
<form id="example-form" method="dialog">
<input name="value1" type="text" placeholder="text-value1" required />
<input name="value2" type="text" placeholder="text-value2" />
<input type="submit" value="Submit" />
</form>
The form element with the example-form
id will have its returnValue
set to Submit
.
In addition to that, the dialog will close immediately after the submit
event is done being handled, though not before automatic form validation is done. If this fails then the invalid
event will be emitted.
You may have already noticed one caveat to all of this. You might not want the form to close automatically when the submit handler is done running. If you perform an asynchronous request with an API or server you may want to wait for a response and show any errors that occur before dismissing the dialog.
In this case, you can call event.preventDefault()
in the submit
event listener like so:
exampleForm.addEventListener('submit', (event) => {
event.preventDefault();
});
Once your desired response comes back from the server, you can close it manually by using the .close()
method on the dialog.
Enhancing the backdrop
The backdrop behind the dialog is a mostly translucent gray background by default. However, that backdrop is fully customizable using the ::backdrop
pseudo-element. With it, you can set a background-color
to any value you want, including gradients, images, etc.
You may also want to make clicking the backdrop dismiss the modal, as this is a commonly implemented feature of them. By default, the <dialog>
element doesn’t do this for us. There are a couple of changes that we can make to the dialog to get this working.
First, an event listener is needed so that we know when the user clicks away from the dialog.
dialog.addEventListener('click', (event) => {
if (event.target === dialog) {
dialog.close();
}
});
Alone this event listener looks strange. It appears to dismiss the dialog whenever the dialog is clicked, not the backdrop. That’s the opposite of what we want to do. Unfortunately, you cannot listen for a click event on the backdrop as it is considered to be part of the dialog itself. Adding this event listener by itself will effectively make clicking anywhere on the page dismiss the dialog.
To correct for this we need to wrap the contents of the dialog content with another element that will effectively mask the dialog and receive the click instead. A simple
<dialog id="example-dialog" class="dialog">
<div class="dialog-container">
<p>This is a modal with some text!</p>
<form id="example-form" method="dialog">
<input name="value1" type="text" placeholder="text-value1" required />
<input name="value2" type="text" placeholder="text-value2" />
<input type="submit" value="Submit" />
</form>
</div>
</dialog>
Even this isn’t perfect though as the contents of the div may have elements with margins in them that will push the div down, resulting in clicks close to the edges of the dialog to dismiss it. This can be resolved by adding a couple of styles the the wrapping div that will make the margin stay contained within the wrapper element. The dialog element itself also has some default padding that will exacerbate this issue.
.dialog {
padding: 0;
}
.dialog-container {
display: inline-block;
padding: 1em;
}
The wrapping div can be made into an inline-block
element to contain the margin, and by moving the padding from the parent dialog to the wrapper, clicks made in the padded portions of the dialog will now interact with the wrapper element instead ensuring it won’t be dismissed.
Conclusion
Using the dialog element offers significant advantages for creating dialogs and modals by simplifying implementation with reasonable default behavior, enhancing accessibility for users that need assistive technologies such as screen readers by using automatic ARIA role assignment, tailored support for form elements, and flexible styling options.
Top comments (0)