Impossible to customize the style of a dialog's ::backdrop residing inside a Shadow DOM.
freshp86 opened this issue Β· 58 comments
Note: Copied from whatwg/html#3601. Please continue discussion here.
More context at https://bugs.chromium.org/p/chromium/issues/detail?id=827397.
Minimal repro 1
This just showcases that any CSS variable is ignored from ::backdrop elements, even without Shadow DOM usage.
https://jsfiddle.net/1zevfdce/3/
Minimal repro 2
Showcases the problem when a <dialog>
resides inside a Shadow DOM.
https://jsfiddle.net/o1trLoqL/2/
Note that the spec mentions the following
"It does not inherit from any element and is not inherited from. No restrictions are made on what properties apply to this pseudo-element either."
Is that the reason for the current behavior? If yes, should the spec be revised, such that customizing the style of a ::backdrop element is possible even if it resides inside a Shadow DOM? Or is there already a viable workaround?
Have you tried without CSS var()
function?
Is that the reason for the current behavior?
Yes. CSS variables are propagated via inheritance into descendants, so if a pseudo-element doesn't inherit from anything, CSS variables which are not defined for the pseudo-element directly would have no effect on the pseudo-element.
dialog::backdrop {
background-color: blue;
}
and
::backdrop {
--bg-color: blue;
}
dialog::backdrop {
background-color: var(--bg-color);
}
appear to render expected result at Chromium.
@guest271314: True, that seems to work, but it still does not solve the issue of the 2nd minimal repro, which involves Shadow DOM.
Have not tried Polymer. Can the code be composed without using a library?
Here is a simple example that puts a <dialog>
inside a Shadow root, without using Polymer. Note that I am not 100% sure that this is entirely equivalent, since it is adding a shadow root on a <div>
element, instead of using a custom element.
You can include <style>
element within Shadow DOM to achieve the same result
let d = document.querySelector('#parent');
let shadowRoot = d.attachShadow({ mode: 'open' });
let style = document.createElement("style");
style.textContent = `::backdrop {
--bg-color: blue;
}
dialog::backdrop {
background-color: var(--bg-color);
}`;
let child = document.createElement('dialog');
child.textContent = 'foo';
shadowRoot.appendChild(style);
shadowRoot.appendChild(child);
let b = document.querySelector('button');
b.addEventListener('click', () => {
child.showModal();
});
@guest271314: Thanks for your effort to help find a workaround.
Having said that, this bug is claiming that there is no proper (for example without using JS) way to style a <dialog>
's backdrop that lives within a custom element. Specifically see these docs (which are not using Polymer), on how the internals of a custom element should be able to be styled from the outside.
Your workaround shows that one can programatically create a dialog
inside a Shadow DOM and, programmatically attach a style to it. It could be potentially useful, but still it seems that the current choice of the spec on how ::backdrop
works, makes it impossible (I would gladly be proven wrong), to style such an element with HTML/CSS code only, such that one can stamp two instances of the custom element, and have instance A use a different backdrop than instance B.
"without using JS" does not appear at OP? Not sure what you mean by "impossible", or how the linked document is related to the HTML standard? What exactly is the the requirement?
such that one can stamp two instances of the custom element, and have instance A use a different backdrop than instance B
You can use the appropriate var()
at CSS and select the specific <dialog>
element https://jsfiddle.net/062m2fhv/7/.
HTML
<button>Open blue dialog inside Shadow DOM</button>
<button>Open green dialog inside Shadow DOM</button>
<div id="parent"></div>
JavaScript
let d = document.querySelector('#parent');
let shadowRoot = d.attachShadow({
mode: 'open'
});
let style = document.createElement("style");
style.textContent = `::backdrop {
--bg-color: blue;
--second-bg-color: green;
}
dialog:nth-of-type(1)::backdrop {
background-color: var(--bg-color);
}
dialog:nth-of-type(2)::backdrop {
background-color: var(--second-bg-color);
}
`;
let child = document.createElement('dialog');
child.textContent = 'foo';
let child1 = document.createElement('dialog');
child1.textContent = 'bar';
shadowRoot.appendChild(style);
shadowRoot.appendChild(child);
shadowRoot.appendChild(child1);
var dialogs = shadowRoot.querySelectorAll("dialog");
let b = document.querySelectorAll('button');
b.forEach((button, index) => {
button.addEventListener('click', () => {
dialogs[index].showModal();
});
});
Can you clarify the parameters of
with HTML/CSS code only
?
Really need @tabatkins and @fantasai's expertise to figure out what the right model for ::backdrop
is I think.
And the other thing we need to know is that if we make it inherit, what would break...
I wonder why was it made not inherit anything? If there isn't really any compelling reason, I guess we should probably just have it inherit from the element.
There wasn't a compelling reason. As I said in the other issue it was to address https://lists.w3.org/Archives/Public/www-style/2012Jun/0441.html and back then we didn't have CSS variables. You don't think there's any observable effects to doing this? I guess most inheriting properties won't have an effect on ::backdrop
.
There wasn't a compelling reason. As I said in the other issue it was to address https://lists.w3.org/Archives/Public/www-style/2012Jun/0441.html and back then we didn't have CSS variables.
OK. I guess that means we can make it inherit from the originating element.
You don't think there's any observable effects to doing this? I guess most inheriting properties won't have an effect on
::backdrop
.
So, by default, that shouldn't affect anything. The only properties directly affect ::backdrop
should be those for positioning/sizing, and border&background. I think those properties are generally not inherited.
It indeed can have observable effects if authors want to, for example, they can say background: currentcolor
which would make the background depend on the inheriting property color
. They can even specify explicit background: inherit
. But I don't really believe that's a common case.
How an element/pseudo-element is rendered has no effect on where it inherits from. It's only a question of where is the element/pseudo-element logically located in the DOM tree.
e.g., ::before
is assumed to be placed at the very beginning inside its originating element, so it inherits from the originating element. But you can always make it cover the whole viewport like ::backdrop
via giving it some proper styles like position: fixed; top: 0; right: 0; bottom: 0; left: 0;
.
In the ::backdrop
case, I think the difficulty is that there is no logical location in DOM tree to place this pseudo-element. Having said that, I realized that there might be some inconsistency if we have it inherit from the originating element, because ::backdrop
is not supposed to be a child of the element at all. But it's probably not a big problem, since top layer would make the pseudo-element escape from any containing block so it doesn't matter whether it is a child of anything in terms of rendering...
@upsuper From the expected effect perspective the only logical element that can fathom which ::backdrop
could inherit from would be <html>
or :root
pseudo class, though the current "inheritance" from ::backdrop
appears to provide a means to utilize specificity as to selectors to display different styles for different <dialog>
elements, unless missing altogether the gist of the current issue.
What about dialog::backdrop
? Isn't it clear in this case that the element resides within a dialog
?
The result of the ::backdrop
affects the entire rendering of the HTML document
. What is the expected result of setting properties and values at dialog::backdrop
? What are you trying to achieve?
Think of Web components (aka custom elements). An element that wraps a dialog
is added to the DOM. The element along with its ::backdrop
are rendered when showModal()
is called. For example see here (which is a copy of actual code from Chromium).
As a developer, a custom element is thought of as a black box with a public API. Having to treat the ::backdrop
as anything other than a child of the custom element is inconvenient, and I don't think is the suggested route anyway. FWIW dialog::backdrop
is already a thing, so I don't think arguing whether the existence of it is justified as opposed to just having a global ::backdrop
needs to be had in this discussion.
What is the issue with the code at the link? :host::backdrop
styles are applied. Still not gathering what the issue is.
@guest271314: I've tried to explain the issue multiple times. See minimal repro 2 at the start of this bug.
CSS variables applied to a shadow root host don't seem to apply to a dialog::backdrop
that resides under that shadow root, which is different than the behavior for any other element under the same shadow root.
Is it not possible to reproduce the result at Firefox?
AFAIK <dialog>
is not implemented in Firefox, so no.
We should just say that ::backdrop inherits custom properties from its originating element, even if we explicitly block all other inheritance.
(This should be doable in pure CSS by saying that ::backdrop { all: initial; --: inherit; }
applies at the UA stylesheet level; this blocks all inheritance of normal properties, but lets custom properties thru. Unfortunately I haven't defined the --
property yet. :( )
I don't think it makes much sense to distinguish between variables and other properties. That may add implementation complexity for no good reason.
Polymer is not currently being loaded at linked jsfiddle. Can the complete issue be reproduced without using a library?
A workaround which gets the style properties of host element from document.styleSheets
which begin with --
, and replaces any matching existing properties and values within <style>
element of Shadow DOM of host element with the value defined at the style sheet
let d = document.querySelector('#parent');
let shadowRoot = d.attachShadow({
mode: 'open'
});
let style = document.createElement("style");
let child = document.createElement('dialog');
let props = [];
for (let sheet of document.styleSheets) {
for (let cssRules of sheet.cssRules) {
if (document.querySelector(cssRules.selectorText) === d) {
for (let rule of cssRules.style) {
if (/--/.test(rule)) {
props.push([rule, cssRules.style.getPropertyValue(rule)]);
}
}
}
}
}
style.textContent = `dialog::backdrop{background-color:var(--bg-color)}`;
shadowRoot.prepend(style);
if (props.length) {
for (let [key, prop] of props)
style.textContent = style.textContent.replace(`var(${key})`, prop)
}
child.textContent = 'foo';
shadowRoot.appendChild(child);
let b = document.querySelector('button');
b.addEventListener('click', () => {
child.showModal();
});
I agree with @upsuperβs comments. Pseudo-elements generally inherit from there originating element, and since they don't contain any text or other content, it seems unlikely that there's a Web-compat reason for ::backdrop
not to do so as well. (If there is, we can take @tabatkins approach of setting ::backdrop { all: initial; }
in the UA stylesheet.)
I recently encountered this limitation. We would typically define our color palette with CSS variables at the document root, and then use those variables throughout components, either assigning to component specific variables or assigning directly to CSS properties. This is my demo.
https://stackblitz.com/edit/dialog-backdrop-test?file=style.css
Polymer/Chromium use a color palette implemented via global <style>
with contents like:
:root {
--google-grey-400: #bdc1c6;
}
Even if we changed :root {
to :root, dialog::backdrop {
at the global level, shadow roots intentionally isolate styles (but allow custom properties to "pierce").
Chromium created a custom element (<cr-dialog>
) w/common styles and logic. It uses a dialog
inside a shadow root:
/* Inside a shadow root. */
dialog::background {
background-color: var(--google-grey-400); /* <-- doesn't work. */
}
Which is why we're having problems.
We might be able to paste parts of the global <style>
inside each and every shadow root (after adding ::backdrop
to the selectors), but that may degrade performance.
If dialog
API exposes ::backdrop
solely via pseudo, it seems that it's essentially specified to work counter-intuitively with variables.
We're pretty seriously thinking of replacing ::backdrop
with a wrapper <div class="backdrop">
because of this issue (which would be a shame, IMO).
CSS Variables don't work in ::backdrop selector for dialogs
https://bugs.chromium.org/p/chromium/issues/detail?id=996467
Because of the ever-widening support for dialog elements, they are starting to become popular.
The sooner we fix this problem, the less webcompat issuse will be.
And I could use it.
::backdrop
is the only place where I have to manually assign the color value.
I'm working on a <dialog>
element-based web component. One requirement is to allow the user to customize dialog background (backdrop area). I keep running into different obstacles with different approaches.
::backdrop
+ CSS variables: not working with shadow DOM (tried putting custom css variables under:root
or:host
or::backdrop
, none of them work.- Export
<dialog>
via CSS shadow parts:part(dialog)
works butpart(dialog)::backdrop
doesn't work. There seems to be no way to customize::backdrop
styles viapart(...)::backdrop
- Roll my own background layer with a
<div>
: when multiple dialogs are open, the backing<div>
of the topmost dialog couldn't cover the dialogs underneath it as<dialog>
elements are in top layer.
I ended up with a traditional approach without <dialog>
.
@mfreed7 do you have any thoughts about how we could resolve this? What approach would a normal web component use to allow this customization, and can we copy that approach in the UA stylesheet?
@mfreed7 do you have any thoughts about how we could resolve this? What approach would a normal web component use to allow this customization, and can we copy that approach in the UA stylesheet?
So this relates to this issue: w3c/csswg-drafts#6641. I think it would be good for you to chime in on that issue, bringing back up the ::backdrop
issue.
It sounds (?) like the current guidance from that issue is to use @property
, which does work for this use case:
<div>
<template shadowroot=open>
<dialog>I'm in the top layer, inside shadow DOM</div>
<style>
::backdrop {
background-color:var(--foo,red);
}
</style>
</template>
</div>
<style>
@property --foo {
syntax: '<color>';
inherits: false;
initial-value: blue;
}
</style>
<script>
document.querySelector('div').shadowRoot.firstElementChild.showModal();
</script>
In Chrome, that shows up with a blue ::backdrop
.
...but they acknowledge that this is a clumsy solution with several shortcomings. I thought I'd post it in case it's a way forward for you.
@tabatkins and I don't understand why ::backdrop
can't just inherit from its originating element. If there's no reason for it, can we just update the spec to say that?
See also #124 (comment)
@tabatkins and I don't understand why
::backdrop
can't just inherit from its originating element. If there's no reason for it, can we just update the spec to say that?See also #124 (comment)
That sounds reasonable to me, and would also fix the problem with custom properties inside ::backdrop
(@jh3y). This might not be web compatible but that remains to be seen.
Yeah, I don't recall why we decided on ::backdrop
, maybe @upsuper does? I guess when it was used only for fullscreen it was more of a "global" thing, but now I'm not sure that makes sense.
@emilio I'm not sure we discussed about it before actually. It not inheriting anything was added in aa8c3ce so maybe @annevk can shed some light.
But as I mentioned in #124 (comment) (and several comments before), I think it makes sense for ::backdrop
to inherit from the originating element.
Thanks for finding the commit. That allowed me to find https://lists.w3.org/Archives/Public/public-webapps/2012AprJun/1319.html. I suspect I just went with what seemed simplest to address @fantasai's comments at the time and @tabatkins agreed with the fix in a subsequent email. If everyone is on board with changing it that seems fine.
Is this still the place for tracking ::backdrop
inheriting (at least CSS custom properties) from its parent element? If so, what is needed to move this along? We are working on a web-component based design system and are running into this.
Looking at https://drafts.csswg.org/css-position-4/#backdrop I think this got fixed as part of moving ::backdrop
's definition into CSS (i.e., 6664387).
If this is not yet universally implemented writing web-platform-tests (or improving existing tests) would help move the implementation process along the most.
Keeping this open until we have good test coverage seems reasonable. Any new issues should be filed against CSS Positioned Layout.
Iβve encountered this issue and found other pseudo elements have the same behaviour, for example ::selection
. It makes things slightly difficult because combining them into one selector with other globals (:root, ::selection, ::backdrop
) will reject the entire style rule on browsers that donβt support one of those elements, which means the entire style rule needs to be duplicated for each selector.
Iβve encountered this issue and found other pseudo elements have the same behaviour, for example
::selection
. It makes things slightly difficult because combining them into one selector with other globals (:root, ::selection, ::backdrop
) will reject the entire style rule on browsers that donβt support one of those elements, which means the entire style rule needs to be duplicated for each selector.
Untested, but isn't :where
a solution here?
:where(:root, ::selection, ::backdrop) {
...
}
:where
certainly helps as it's forgiving; if it aligns well with your browser support matrix. It's well supported enough that that is likely the case, but there are still in-the-wild browsers that don't support it.
Pseudo-elements in :where()
are also not valid. So, not a solution. In any case this is fixed at the spec level. People should file browser bugs (and if possible, write tests).
https://bugzilla.mozilla.org/show_bug.cgi?id=1855668 for Firefox.
I also wrote a patch + fixed tests, so I guess that's not needed anymore.
One thing that came up while fixing tests were tests that were doing:
dialog { visibility: hidden }
And expecting the backdrop to render. I don't expect that to come up in practice, but if such thing became a backwards-compatibility issue, we'd need to do something like adding this to the HTML style sheet:
dialog::backdrop { visibility: visible }
Again, I don't think we should do this unless required by compat.
Seems like this is tracked in chromium with https://bugs.chromium.org/p/chromium/issues/detail?id=827397 (I tested in chrome with the BackdropInheritOriginating
flag enabled and it worked, CSS var passed to ::backdrop
).
Anyone know if there is a webkit bug for this? I couldn't find one.
Update: I created https://bugs.webkit.org/show_bug.cgi?id=263834
https://bugs.webkit.org/show_bug.cgi?id=263834 is now resolved.
Is it worth closing this issue out now?
@keithamus from that PR it seems like the only coverage for this is html/semantics/interactive-elements/the-dialog-element/backdrop-inherits.html
? Which doesn't address fullscreen. I suppose it might be okay, but it doesn't seem ideal.
Right of course good point.
I'm adding a test for this in web-platform-tests/wpt#43445.
Glad to see Chrome will be shipping soon as well: https://groups.google.com/a/chromium.org/g/blink-dev/c/yXTxBfLthzc