Against the backdrop
Since I recently wrote about resetting <dialog>
and popover
styles styles, I guess I also have to talk about the ::backdrop
pseudo-element.
I’ve been wanting to write about ::backdrop
for quite some time now. I used to feel very strongly that ::backdrop
is an utterly useless API. Thankfully, one of the biggest issues with ::backdrop
(inheritance) was recently fixed in all browsers, which means ::backdrop
is starting to become more viable than it was in the past.
The remaining issues I mention in this post are also serious problems that I would love to see solved at the platform level, but these are much easier to work around (even if the best workaround is to not use ::backdrop
in the first place).
Bad default styles
The ::backdrop
pseudo-element comes with the following user-agent styles (reconstructed by inspecting in browser dev-tools and reading the HTML standard):
::backdrop {
display: block;
position: fixed;
inset: 0;
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.1);
}
:popover-open::backdrop {
pointer-events: none !important;
background-color: transparent;
}
I’m highlighting the problematic parts:
- The
background
ondialog::backdrop
has an opacity of 10%. This is too low, in my opinion. The content behind the backdrop is not dimmed enough, which can lead to frustration among users who expect that content to be clickable1. Thankfully, this is quite easy to override in author-land. - The
pointer-events: none !important
declaration on:popover-open::backdrop
is… awful, to put it nicely. It might make sense as a default (since it is also transparent by default), but it uses!important
, which means it cannot be overridden by authors.2- To understand this issue, just visit MDN’s blurred backdrop example. Try clicking the “Show popover” button to show the popover, and then try clicking it again. What you’ll find is that your click goes through the blurred backdrop and reaches the button underneath.3
You can’t “click” it
When it comes to popover
, you literally cannot click the ::backdrop
because of pointer-events: none
(as discussed above).
As for <dialog>
, you can technically click the ::backdrop
, meaning it intercepts clicks. But there is no backdropclick
event to listen to.4 Practically, this means you need to listen to clicks on the <dialog>
element, and add an extra wrapper element around all your actual content.5 But if you’re doing this, why even use the ::backdrop
at all? You can make the <dialog>
fill up the viewport and effectively act as a backdrop.
Or better yet, use a box-shadow
that covers the entire viewport.
dialog:modal {
box-shadow: 0 0 0 100vmax #0005;
&::backdrop {
display: none;
}
}
And then you can listen to clicks on the document
6.
dialog.ownerDocument.addEventListener("click", (event) => {
// Do nothing if it's not a modal dialog
if (!dialog.matches("dialog:modal")) return;
// Close the <dialog> if clicked outside, i.e. on the <html> element
if (event.target === dialog.ownerDocument.documentElement) {
dialog.close();
}
});
(Bonus: By avoiding ::backdrop
, animation also becomes easier, since any animation technique used for the main <dialog>
will automatically also cover the custom backdrop).
What do?
The <dialog>
element’s ::backdrop
is workable, with some elbow grease. I can live with it, and I’m being forgiving because <dialog>
has been around for a while.
The popover
attribute’s ::backdrop
is unusable in its current state. I would create a custom backdrop, using something like an empty <div>
.
Footnotes
-
You might think that the presence of the dialog itself is a clue that the rest of the page is inert. However, this is not true for everyone. Users who browse the web at a high zoom level or using screen magnifiers may only be able to see a limited portion of the screen, which means the dialog could be off-screen when the user is looking at the content beneath the backdrop. ↩
-
User-agent styles declared with
!important
cannot be overridden by authors, even if the author styles are declared with!important
. ↩ -
Another problem with blurring the backdrop like this is that it can obscure focus, violating WCAG SC 2.4.11. This is easily reproducible when you open the popover using your keyboard or when you Tab out of it. ↩
-
The lack of a
backdropclick
event seems to be an intentional choice. There’s some discussion around adding light dismiss functionality to<dialog>
, avoiding the need for authors to listen for backdrop clicks in some cases. I’m wary of this promise. Even looking past the bad track record, I worry that the platform might decide that “light dismiss” includes interactions that don’t fit my personal definition of “light dismiss”, in which case I’m forced to implement my own custom backdrop. ↩ -
It might feel weird to listen for clicks on the
<dialog>
, but it makes sense when you think about it:::backdrop
is a pseudo-element, so of course the event listeners will go on the parent<dialog>
. I created a CodePen some time ago if you’re interested in the full code. ↩ -
Side note: I don’t particularly like that I have to use
matches
to check if the dialog was opened using.showModal()
. I also don’t like that there is no"open"
event I can listen to, even though there is a"close"
event. ↩