Modern modal dialogs often support "light-dismiss," allowing users to close a dialog by clicking or tapping the backdrop (the area outside the dialog). The closedby attribute provides a declarative way to enable this behavior without custom JavaScript.
Implementation
To enable light-dismiss:
- Add
closedby="any"to the<dialog>element. - Open the dialog using
dialog.showModal().
Attribute Values
any: Enables light-dismiss (clicking the backdrop), "close requests" (theEsckey), and developer mechanisms (e.g.,dialog.close()).closerequest: Enables "close requests" and developer mechanisms only. This is the default for modal dialogs.none: Only developer mechanisms can close the dialog.
Styling the Backdrop
When a dialog is opened as a modal using showModal(), the browser generates a ::backdrop pseudo-element. This backdrop covers the entire viewport and sits directly behind the dialog.
/* Style the backdrop to indicate the dialog is modal */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px); /* Optional: add blur for modern browsers */
}
Example
<!-- MANDATORY: Use closedby="any" to enable light-dismiss behavior -->
<dialog id="myDialog" closedby="any" aria-labelledby="dialogTitle">
<form method="dialog">
<h2 id="dialogTitle">Feedback</h2>
<p>Click outside this box or press Esc to dismiss.</p>
<button type="submit">Close</button>
</form>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button>
Constraints & Accessibility
- MANDATORY: Use
closedby="any"to enable light-dismiss declaratively. - MANDATORY: Always open modal dialogs with
showModal(). This ensures the dialog is in the top layer, focus is trapped, and theEsckey is handled. - DO: Ensure that there is always a close button visible to provide people with a clear, alternative way to close the modal dialog.
- DO: Use
aria-labelledbyoraria-labelto provide an accessible name for the dialog. - DO NOT: Use
closedbyfor non-modal dialogs (opened withshow()), as they do not have a backdrop and won't trigger light-dismiss. - DO NOT: Use the
clickevent for critical logic that should happen before closing; instead, listen for thecloseorcancelevents.
Ensure that there aren't components in the modal dialog that themselves use the Esc key for interaction, as this may lead to the dialog being inadvertently closed. If the modal dialog does include components that use the Esc key (e.g. a menu or a combobox), ensure that when these components are focused, they capture the Esc key press and use it accordingly, and that the dialog is not closed accidentally.
Fallback strategies
MANDATORY: For browsers that do not yet support closedby, you must implement a fallback for light-dismiss by checking if a click occurred outside the dialog content's boundaries using the following script:
const dialog = document.querySelector('dialog');
// Fallback for browsers without closedby support
if (!('closedBy' in HTMLDialogElement.prototype)) {
dialog.addEventListener('click', (event) => {
// 1. When clicking the backdrop, the event target is the dialog element itself.
// Ignore clicks where the target is a child element inside the dialog.
if (event.target !== dialog) return;
// 2. Check if the click coordinates fall within the dialog's content box.
// This distinguishes between a click on the backdrop vs a click on the dialog's background/padding.
const rect = dialog.getBoundingClientRect();
const isDialogContent = (
rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width
);
if (isDialogContent) return;
// 3. Since the click was outside the content area (on the backdrop), manually close the dialog.
dialog.close();
});
}