Proposal
Allow a user to define a set of styles to apply to a custom element as an option to customElements.define. Conceptually, providing styles would make the element act as if it had a shadowRoot including a style element with the provided css. The rules used to target the element would be the same as in Shadow DOM.
customElements.define("cool-element", CoolElement, {styles: ':host {display: block; }');
Discussion
To style a custom element that does not otherwise need a shadowRoot incurs an unfortunate performance penalty and is cumbersome. A user must create a shadowRoot and put inside it a style element and a slot element. Providing this styling at define time gives the platform an opportunity to optimize beyond what could be achieved when interpreting user code that installs the shadowRoot, style, and slot at construct/connected time. In addition, because the proposed syntax is less code than the alternative, it would likely reduce concern over https://github.com/w3c/webcomponents/issues/426.
Ideally, developers could include styles targeting elements inside Shadow DOM in addition to the element itself. Again, the provided styles would act as if they were in a style that was the first element inside the shadowRoot. This could help address https://github.com/w3c/webcomponents/issues/282 and the feature could be explained as a constructable stylesheet when this feature is added to the platform.
I like this idea a lot. One thing about it that it solves is the issue of wanting to supply a "UA stylesheet" for your custom elements. You can't just do something like my-element { display: block; } because this fails to reach inside shadow roots. When we had deep, you could do something like my-element, * /deep/ my-element { display: block; }, but deep is gone. Also, that formulation is higher priority than the UA stylesheet, as noted in HTML as Custom Elements. (In particular, something like * { display: inline; } should override this "UA stylesheet level" stuff.)
One thing I am still confused on is whether :host is the right name for the selector targeting the specific element being defined. Since we're specifically _not_ talking about inserting a shadow root, and :host is currently defined in terms of shadow DOM, it seems like a bad match to me, and I'd expect something new, more like :element. But others have told me :host is a good idea. If we sufficiently redefined :host as such (@TabAtkins, @hayatoito?) then maybe it's OK. I'd still like to understand why we'd do that though.
+1 from me on this idea; I'd wondered about an author-controlled "UA styles" a few years ago, early in the project, and this API surface feels good, better than whatever else I'd come up with.
I presume that the selectors are meant to be interpreted "like" a shadow stylesheet, right? So they only apply to the element and its contents, and don't leak out to the global level? In that case this is pretty easy - just need to specify that the styles are interpreted in the user-agent origin and all of its selectors are scope-contained by the host element.
I think using :host is fine - it has a very similar meaning here to what it does in shadow DOM, and that makes things easier to understand. Do we plan to "hide" the host element, like you get in real shadow DOM? (I think we should, to keep the similarities as high as possible.) If so, then we just need to additionally say that, for the purposes of these selectors, the root element is featureless and matches :host/:host()/:host-context(). (This is more-specific-overriding-general, contradicting the general rule that :host/etc represents nothing outside a shadow tree.)
Question: do we want to _necessarily_ tie this to the registerElement() call? Or do we want to allow embedding this into your global CSS too, so that even if your JS is slow or broken, your page at least doesn't totally break? We'd put it under a @custom-element foo-bar {...} rule or something similar. We'd just need to define an ordering for the JS sheet vs the CSS ones; I suspect the JS sheet should come afterwards, so they'll win in case of conflict.
See also https://github.com/w3c/webcomponents/issues/376 (closed), where I had a similar idea.
I really like this idea too, though it might be worth having a custom element-specific stylesheet instead:
let sheet = document.createElement('style');
sheet.textContent = ':host { display: block; }';
customElements.define("cool-element", CoolElement, { styleSheet: sheet });
This way you can retain a reference and add to, remove or modify the css rules in a familiar manner later if required.
Yeah, having the IDL be (DOMString or CSSStyleSheet) would be good. Don't want to _require_ the object-creation dance if it's something simple.
See also #376 (closed), where I had a similar idea.
Interesting! Do you think this more limited approach (just giving a stylesheet to a custom element) is sufficient, or do you still think we need the ability to target arbitrary elements with a compound selector? I can see arguments either way.
(I think we need this current idea either way; providing default styles for a component is good and useful all by itself.)
Interesting! Do you think this more limited approach (just giving a stylesheet to a custom element) is sufficient, or do you still think we need the ability to target arbitrary elements with a compound selector? I can see arguments either way.
It looks that each solves the different use cases. If a selector used in "a stylesheet to a custom element" always has a ":host" pseudo class, both approaches might be able to address the original concern.
If we can assume that ":host" is always used in a selector here, can we have a more lightweight approach?
e.g.
customElements.define("cool-element", CoolElement,
{style: "* {display: block; } *[red=true] {color: red; }",
shadowtreestyle: ... /* if we still need this */ } );
Thus, I would like to limit more and more so that it can become style-engine friendly, ignoring a shadow tree in most cases.
That sounds reasonable to me! I think I agree that if you want to style the custom element's contents more fully, you should probably be using a shadow tree. The behavior of styles in shadow trees already works pretty well.
So this brings your idea from #376 fully in line with the idea from this thread; the {style: "..."} option just provides a convenient inline mechanism for defining such styles on the element, without having to repeat the tagname over and over if you're defining multiple selectors. We can then lean on your @global-compound-selector-rule (with a better name, of course ^_^) for the declarative side of things, or if people want to provide the styles in their CSS file rather than in their JS file. (And we can do it in the future; no need to block this thread's idea on figuring out the details of the CSS rule.)
Yeah, I do not have an intention to block this thread's idea. I'm totally fine to let customElement.define take a style option.
From the conference: There's no real objection to this proposal, but it needs to be more worked out with respect to the CSS cascade and such before it can be properly reviewed. Another concern that was raised is that it would help if there was some kind of holistic overview to styling custom elements and shadow DOM since there appear to be several overlapping approaches.
holistic overview to styling custom elements and shadow DOM since there appear to be several overlapping approaches.
That makes sense. Here the scenarios we've run into so far that need styling:
display so the page doesn't render totally screwed up. Preferably this should be possible without requiring a shadow root. This must apply thru shadows; that is, it can't be done by a global stylesheet just applying a x-foo {...} style.Which idea of Hayato are you referring for (4)?
The one that he and I were talking about immediately prior to this, from issue #376.
@sorvell, does @hayatoito's simpler approach in https://github.com/w3c/webcomponents/issues/468#issuecomment-203807233 fit your use cases, or do you also need to be able to style descendant elements?
@tabatkins, would you have some time next week to work on defining this (either @hayatoito's proposal or something closer to the OP) as a more fully-fleshed-out proposal, with maybe some proto spec text? I think we'd need your help (or someone else great at writing CSS specs) to actually figure out how this means, and maybe put the relevant stuff in CSS scoping. How I envision this is HTML just defining the dictionary member and saying something like "this _creates a custom user-agent stylesheet_ for the current Window with element name _name_," where you can define "creates a custom user-agent stylesheet" for us.
Yeah, def.
@domenic Yes, I think #468 (comment) is a reasonable simplification.
To be clear, I do not think it's a good idea to expose the ability to style descendants (children). The initial proposal did include styling shadowRoot elements (shadowRoot children), but this can always be addressed in the traditional way. Since you're already making a shadowRoot in that case putting a style element there is straightforward.
@hayatoito's approach solves the fundamental problem here: an element has no desire to create a shadowRoot and just wants to style itself cheaply and easily.
Wow, didn't expect this issue to be open just 2 days after #376 was closed.
This proposal actually covers all that I expected in #376, so thanks @sorvell for raising it once again.
@domenic
(In particular, something like * { display: inline; } should override this "UA stylesheet level" stuff.)
What about some options to disable all default UA styles?
el.createShadowRoot({defaultStyles: false})
From my lesser understanding of all Web Components compared to you all, it seems as though ShadowDOM roots are designed to be the units of encapsulation for DOM. If that is the case, it seems like encapsulating styles in this programmatic manner might be a better fit for the el.createShadowRoot method. For example:
el.createShadowRoot({styles: ':host {display: block; }')
This particular example seems to make more sense because :host refers to the host of a shadow root where the style is located, making createShadowRoot() seem like the obvious choice over createElement() or define(). There's nothing that prevents a Custom Element from having multiple shadow roots, so :host in the style of a custom element is ambiguous (as implied by @tabatkins above). @tabatkins also brought up that there would need to be a new mechanism to scope the style to the custom element (because there's no extra benefit to the API addition if it just creates global style), but such a style scoping mechanism already exists on ShadowDOM roots.
TLDR, would it make sense to move this programmatic definition of a style onto shadow roots where style encapsulation already exists, instead of on Custom Elements where we have to define new mechanisms to deal with possible ambiguities and style scoping issues?
There is at least one case when it is not possible - when you extending native elements. They have UA's ShadowRoot, but you neither have access to it, nor have ability to create new one on them, look at #376 for more discussion. Also this particular issue is exactly about avoiding performance overhead of creating Shadow Root (see the first message in this thread).
@nazar-pc Thanks for pointing that out. Maybe we can have both?
customElements.define("cool-element", CoolElement, {styles: '.thing { color: blue; }');
el.createShadowRoot({styles: ':host {display: block; }') // using :host makes sense here
@domenic All right, first draft is up. I forgot to give the heading an ID, so just visit https://drafts.csswg.org/css-scoping/#shadow-dom and scroll up.
Let me know if this suits your needs and if you need anything changed. Idea is that DOM would parse the string to a stylesheet and then manipulate the [[defaultElementStylesMap]] itself.
@tabatkins that looks great! Where's the algorithm for parsing a string into a spec-stylesheet that I can use as the value in a map entry?
https://drafts.csswg.org/css-syntax/#parse-stylesheet
We're having a hard time following this discussion because there is a lot of different use cases and ideas being discussed here. Could someone compile a list of concrete use cases that are meant to be addressed by this issue?
I think there are two main cases:
We've encountered a number of elements that create a shadow root only to apply host styling. Their shadow only consists of a <style> and <content>. The cost of the shadow root definitely shows up when performance tuning large apps. With this we can eliminate the shadow.
Currently this requires creating a <style> element in each shadow, which has costs. Allowing styles to be passed in as a string at definition time is a path to allowing constructible stylesheets to be passed in later.
Additionally, it may address parts of #426 by allowing elements to set display: at definition time, without needing to create a shadow root. This is really just the first use case, and it probably doesn't address the default value without some helper adding in display: block value by default.
@sorvell any more from the Polymer customers?
To state the "Styling elements that do not otherwise need a shadow root" use case in a way that is more custom elements-centric:
It's common for custom elements to want to come with default styles---very similar to the "user agent stylesheet" for built-in elements.
You might think that the way to do this is to ship my-element.css in addition to my-element.js, with contents like my-element { display: block; }. (Or something much more complicated, for my-button.)
However, this does not work for two reasons:
<button> inside a shadow tree, it is styled, but with the above solution, a <my-element> inside a shadow tree stays display: inline, since the my-element selector cannot reach inside shadow trees.The solution proposed here lets custom elements come prepackaged, at definition time without any external dependencies, with default "user agent" level stylesheets. It also allows modifications such as styling conditional on the presence or absence of an attribute, e.g.
customElements.define("my-button", MyButton, { styles: `
* { display: inline-block; }
*[disabled] { color: gray; }
`);
Allowing styles to be passed in as a string at definition time is a path to allowing constructible stylesheets to be passed in later.
This doesn't actually require constructible stylesheets; engines can memoize on the string values. Last time this was discussed constructible stylesheets were more useful just to make the sharing explicit to the developer, and don't provide much, if any, advantage for the engine.
Does this help, @rniwa?
Thanks.
- Styling elements that do not otherwise need a shadow root
Could you give us a concrete list of those elements?
- Sharing styling across elements
We (WebKit) already have a plan to implement an optimization similar to the one @domenic pointed out without needing this new API.
- Most importantly, because it does not work inside shadow trees. When you use a
The solution here is to attach a shadow root on a custom element where such rules need to be applied. Note that we've repeatedly and consistently opposed to having type extensions supported in custom elements so we're less sympathetic to the argument that this is needed for builtin elements that don't support shadow roots.
- Less importantly, but still interestingly, is that the precedence of such stylesheets is higher than the usual precedence of "user agent stylesheets" in the cascade. This has various subtle undesirable side effects.
Could you list a concrete use case and what those undesirable side effects are?
- Styling elements that do not otherwise need a shadow root
Could you give us a concrete list of those elements?
This is an unbounded set of author-created elements, so no, a "concrete list of those elements" can't be provided. Justin can probably give a few examples from their own code, tho.
The solution here is to attach a shadow root on a custom element where such rules need to be applied.
As Justin said, in cases where you don't actually need a shadow tree (your custom element is a container with special powers, that doesn't need to provide any special shadow structure), requiring a shadow root + style element is a cost without a benefit.
Could you list a concrete use case and what those undesirable side effects are?
We're just talking about standard specificity battles. You don't have to worry about fighting with another selector when overriding UA styles; they lay the default groundwork, and you can override freely; even if they respond to state in some way, which would require higher-specificity selectors, you can still override without worry.
- Styling elements that do not otherwise need a shadow root
Could you give us a concrete list of those elements?This is an unbounded set of author-created elements, so no, a "concrete list of those elements" can't be provided. Justin can probably give a few examples from their own code, tho.
I think you misread my comment. I said a "concrete list", not a "complete list".
As Justin said, in cases where you don't actually need a shadow tree (your custom element is a container with special powers, that doesn't need to provide any special shadow structure), requiring a shadow root + style element is a cost without a benefit.
We don't think having an extra shadow root and a style element is a cost compared to the cognitive cost of having to remember yet another way to specify style rules. Now in addition to UA & user stylesheets, style rules in document, shadow trees, and inline style declarations, authors need to be aware of style rules that come from custom element definitions. All these complexities makes it harder to authors to reason about already convoluted style resolution. We don't want to make web components so complex that only people working on browsers and libraries can understand.
Could you list a concrete use case and what those undesirable side effects are?
We're just talking about standard specificity battles. You don't have to worry about fighting with another selector when overriding UA styles; they lay the default groundwork, and you can override freely; even if they respond to state in some way, which would require higher-specificity selectors, you can still override without worry.
Why does having a style element inside a shadow tree not solve this problem? Rules in such a style element has a different cascading order than the one author apply outside the shadow tree.
I think from a component standpoint, having something that gives authors access to user-agent-level rule evaluation is huge. It doesn't really make sense for the masses, which is https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Default-Stylesheets-Concept-and-Proposal.md probably isn't a good idea, but limiting to the custom element scenario seems totally legit (since UA's have default stylesheets for elements, why isn't this exposed to authors of new custom elements?)
Why does having a style element inside a shadow tree not solve this problem?
Honestly, from the perspective of an outsider who doesn't develop browsers -- a mere "web developer" -- yet library author working on http://infamous.io, putting styles inside a shadow tree in order to style the shadow host (thus breaking out of the shadow encapsulation back into the light tree) seems exactly like a hack.
Specifying styles directly onto the custom element definition is much cleaner because the style is associated directly with the element being styled (not with it's shadow root), and I appreciate the cognitive load required to work with that much more (@rniwa) in terms of code clarity, separation of concerns, and maintainability. This is what led me to ask #490.
Suppose we have
customElements.define("custom-element", CustomElement, {styles: '.thing { height: 50%; }');
Will the height of div.thing be 50% of the element where it is finally located (i.e. the element in the flat tree where div.thing finally lives)? Or 50% of the height of the <custom-element>?
div.thing cannot be styled by the styles option. Only custom-element elements can be styled by the styles option.
Your example would ensure that custom-element elements with class thing would have the style height: 50% on them. This would then behave according to normal CSS rules for how height behaves.
@domenic So the * selector in
customElements.define("my-button", MyButton, { styles: `
* { display: inline-block; }
*[disabled] { color: gray; }
`);
only selects the custom element that the style is defined on?
Correct.
What is the status of this issue? I guess there is no progress.
@sorvell
If we support this feature, can we remove the most usages of :host pseudo class? I think that would be a win.
The CSS side was complete back in April, defined in https://drafts.csswg.org/css-scoping/#default-element-styles. Only thing that needs to be done is the additional argument to registerElement().
customElements.define("my-button", MyButton, { styles: ` * { display: inline-block; } *[disabled] { color: gray; } `);
That seems funky for some reason.
But, while we're at it, we may as well decide to allow passing CSSStyleSheet(text, options), something like
const sheet = new CSSStyleSheet(text, options)
customElements.define("my-button", MyButton, {
styles: sheet
);
I'm not a huge fan of defining the styles as a string either. But presumably UA styles are going to be pretty small so I can begrudgingly live with it.
I like the idea of it accepting a CSSStyleSheet but I would want that stylesheet to be defined in html via a <style> tag and doing that would have unwanted side-effects. So if the only way to use a CSSStyleSheet is by creating it in JavaScript and appending text to it, I don't see it as an improvement.
FWIW, I like every proposal as long as it works but I am having hard time to understand the plural as object property ... styles ... why is that?
<style>, not <styles>{style: sheet} is more semantic than {styleSheet: sheet} (which is even worse than styles, IMO)style or css to style their components.style, never styles, here just an exampleAs silly as this matter could look like, I think having style as property name doesn't need explanations, while having a plural form would mislead at least once most developers (those I know).
You don't "_styles_" a component, you "_style_" a component.
As non native, non mother-tongue, English developer, I think the correct naming would be a plus.
Thanks in advance for eventually considering this point of view.
FYI - I posted a thread about constructable stylesheets at WICG discourse
https://discourse.wicg.io/t/proposal-constructable-stylesheet-objects/2572
@jcgregorio's blog post has a (somewhat synthetic but nevertheless impressive in the effect) demo case at the bottom of the prose that can serve as a motivation for having this proposed mechanism.
For customElements in particular, why not allow for the defining of a custom stylesheet map, such that
customElements.defineStyles('some-map-key', CSSStyleSheet | string | href?);
would set up a map of styles that would only apply to a custom element that somehow references that map key? I can see that going one of two ways:
class MyElementConstructor extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open',
styles: ['some-map-key', 'some-other-key']
});
}
/* omitted */
}
or
customElements.define('my-element', MyElementConstructor, {
styles: ['some-map-key', 'some-other-key']
});
Thus that any styles attached to 'some-map-key' or 'some-other-key' would be added only to this element, but not the surrounding window. I can see how someone might argue this would break encapsulation, but would allow browsers to optimize rendering without bleeding into the global CSS scope.
It seems this work is blocked on spec'ing constructible stylesheet? FWIW, only requirement we have is that this feature to NOT add a new cascading order.
Why would this be blocked per the constructible stylesheet? I think it should definitely plan to incorporate such a feature, but I don't see any reason why we couldn't load in an external stylesheet to be applied or pass in a string for styles. It might not be perfect, but it _should_ work (although I'm fairly ignorant about browser internals, so I'll accept any feedback on that front).
Also, I do think that following Angular's pattern of applying styles as an array is preferable. I can foresee some cases where a user might want to set up a single stylesheet to set up CSS variables or @apply rules that might be used across multiple components. It would be a big benefit to be able to define that once and reuse across multiple custom elements. Using a syntax similar to what I defined above you could do:
class MyLibraryComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open',
styles: [ 'my-library-variables', 'my-library-component' ]
});
}
}
OR
customElements.define('library-component', MyLibraryComponent, {
styles: [ 'my-library-variables', 'my-library-component']
});
customElements.defineStyles('some-map-key', CSSStyleSheet | string | href?);
This is an interesting idea!
I'd go with placing styles on the element, NOT the shadow root. I have many custom elements elements that don't have a shadow root, so adding one just for this functionality would be too heavy.
Why would this be blocked per the constructible stylesheet?
Yep, doesn't need to be blocked, it can just accept a CSS string for now. And href is not needed, @import can be used in the string.
FWIW, only requirement we have is that this feature to NOT add a new cascading order.
What do you mean? Or do you mean these styles should have highest priority, second only to UA styles?
It seems to me like the order they are defined in the array would dictate Cascade order, and that this would helpful.
It seems to me like the order they are defined in the array would dictate Cascade order, and that this would helpful.
I would also assume that document-level styles that would typically bleed into the shadow DOM would still override (so CSS variables, things like color, font-family, etc.).
@rniwa, do you thing having CSSStyleSheet _should_ be a blocker for this or would Apple be cool with allowing either a CSSStyleSheet or a text string (dropping href per @trusktr's comment above)?
I don't think we can make text string work because that would involve loading resources as a part of custom element definition, and all sorts of resource loading related questions arise. e.g. what happens with CORS, subresource integrity, referrer policy, etc...
@rniwa, Frogive my ignorance, but how would a CSSStyleSheet object be any different? They could feasibly require resources from other origins, just like a link element could. It’s up to the consumer and author of the element to ensure everything is on the up-and-up as with any other feature that involves the loading of assets.
I like the general motivation, but not sold on this implementation. How Is this different from @ apply (assuming it will be eventually implemented and will work cross shadow boundary)?
I say this as a non-designer backend developer, who occasionally has to put output on the screen, but its not large enough portion of project to hire dedicated designer. I grab some nice ThemeForest bootstrap template, strip away parts I don't need, and apply MVC to it, such that, say, input group HTML blob goes into CustomElement's shadow root, and make JS api through attribute change handlers to interact with it. The problem here, is those CSS themes (emphasis on themes) by their nature cannot be cleanly broken down into independent components. Eg, all have a certain border and background style, be it a text field, or a panel. There are very large style inheritances that in current and proposed (i think) way, need to be duplicated per component (preprocessors at least makes it automated). Occasionally we do call a designer guy, but same principle applies: development of looks is very separate from development of custom elements, and does not follow same code hierarchy and inheritance rules.
The map approach, unless I do not understand it, still will require me to extract element-specific chunks of global theme into defineStyles map together with large chunks duplicated, if these style keys are encapsulated. Developers who do not know any better will just stuff entire CSS libraries into each component. Eg, for a text field I have to include input-group, form and all of the base, which includes all type of fields and basic page layouts, not just text input that I want, and even in this this new proposal occurs per style definition. So in those cases I use light DOM, but it defeats the purpose of having a smaller DOM tree to look through in inspector.
I can see how someone might argue this would break encapsulation,
For themes outsourced/created by a designer and then passed on to a coder this is actually a good thing. That's why proposed https://tabatkins.github.io/specs/css-shadow-parts/ in chrome 63's release notes is a good compromise to removal of deep selectors. I can extend designer's theme by adding my deliberately exposed parts of shadow dom as additional style selectors of main theme. Or assuming apply rule can talk to parent document from within shadow root, I tell my element that it should apply some style I bought/outsourced.
Well, probably need to disallow @import in https://wicg.github.io/construct-stylesheets/index.html as well. However, the reason I said being it's being blocked by the constructible stylesheet is not so much that we need that CSSStyleSheet constructor but rather we need to define those semantics. Getting all the details right is a rather tedious task. If someone can specify every detail & get buy-in from every browser vendor, then we don't need constructible CSSStyleSheet. All I'm saying is that adding this feature requires solving all the problems we have for constructible CSSStyleSheet.
@alextech
How Is this different from @ apply (assuming it will be eventually implemented and will work cross shadow boundary)?
Even if @apply were to be cross-shadow, we'd need to add a shadow root (with a style element in it) to every element that needs to have a :host style @applyd, even if the element _will not have_ any content in the shadow root except a <slot> element just so light content renders. This is lots of overhead: for example, for 1000 elements that don't need a shadow root other than to apply host styling, we will instantiate 1000 shadow roots and 1000 style elements. Compare this to instantiating 1 string mapped to 1 style sheet; applying the style to all 1000 elements with just the 1 string and 1 stylesheet.
@apply works inside of a stylesheet, while this new mechanism is a way to apply a stylesheet from outside a stylesheet (not CSS), and so with this map we can define how styles are applied in way that isn't as heavy as the shadow DOM approach.
But in the topic of @apply, it could be possible to just make an out-of-CSS mechanism to apply custom properties on a custom element. F.e.:
class MyEl extends HTMLElement {
static get styles() { return [
"--some-rules", "--other-rules"
]}
}
or
customElements.define('my-el', MyEl, {
apply: [ "--some-rules", "--other-rules" ]
})
But that might be less flexible. Stylesheets support features besides rules (media queries, animation keyframes).
@trusktr good point, forget my apply suggestion. In that case, your styles getter to list selectors that custom element is allowing the document to modify it with is good. For cases where element styles are indeed encapsulated away from main document, not themed, define can maybe take style url :
customElements.define('my-el', MyEl, {
style: {
apply: [ "--some-rules", "--other-rules" ],
href: "elementSpecific.css"
}
})
@alextech, I would assume any proposed style mechanism would need to be either a single item (string identifier or CSSStyleObject) or any array (an indexed iterable) so that the cascade will be predictable based on some internal algorithm.
The original proposal only called for a single value, but I do believe that using an array would be more flexible, but @rniwa or another browser expert might have a reason that might not work. I would assume, though, that any @apply rules would be set in the style sheet object passed to the style parameter on the element definition config object.
I do like the idea of defining these styles in a registry that can be referenced/recalled/reused as needed. I’d really like to hear @sorvell’s (original issue filer) thought on that addition to the original request.
EDIT
Thanks for answering that question, @rniwa, that doesn't entirely register with me, but I'm happy to attribute that to my lack of understanding/familiarity of browser internals (my assumption was that it would be no more or less complicated than applying a style tag, but again, that's my ignorance talking).
How Is this different from @ apply (assuming it will be eventually implemented and will work cross shadow boundary)?
Note that @apply is officially abandoned and won't be implemented, so any discussion relying on that is moot.
Is there an issue or repo where I can follow the discussion around CSSStyleSheet (other than the aforementioned document)? I'm really interested in this particular issue and would like to know as much as I can (and help if possible).
EDIT
Nevermind, found the issue tracker here.
Tokyo F2F: There was a consensus to add this feature. The stylesheet would be treated as if it's the first style or link element inside the shadow DOM of a custom element instance. The shadow root doesn't exist, and attachShadow can be called later. In that case, the style defined via custom elements will retain but won't be exposed as one of the stylesheets on ShadowRoot.
This feature would work with styleSheet of a style or link element as well so we're not necessarily blocked by the constructible stylesheet.
We need to confirm with CSS WG that :host works in this setup.
Ok, so this acts like it's the first sheet in the shadow; if there's no shadow, it still acts like there's one, and it's the only thing in there. That's nice and consistent.
We need to confirm with CSS WG that :host works in this setup.
Yeah, as long as the stylesheet exists in the shadow tree (or some imaginary shadow context), :host will work.
It's the defining of "some imaginary shadow context" that will take some creativity :). We'll try to take a look at what CSS needs to pretend it in a shadow context, and hopefully find a clean way of saying "imaginary shadow context for CSS purposes" on a spec level.
I think the "style sheet list" we're talking about here basically needs an associated "host element" and "shadow root" pointer. And it needs to allow for the latter to be null. And define matching in terms of those two pointers.
@rniwa I don't think <style> elements have a styleSheet unless they are attached and rendering. Therefore, I think this is still somewhat blocked on constructible stylesheets.
@azakus you could do it in an <iframe>. It's a little ugly, but it'll work.
@annevk @rniwa Is the current consensus to add a single style object or some sort of collection (i.e. a single CSSStyleSheet or a StyleSheetList/ Array<CSSStyleSheet | HTMLStyleElement | HTMLLinkElement>)?
@calebdwilliams I would think it is a single CSSStyleSheet.
@annevk I suppose a simpler hack would be to make a dummy element in the main page, attach a shadowRoot, and then stuff your styles in there, but I'm a bit sad I have to resort to a hack.
@azakus That's kind of what I'm afraid of. I would really like the freedom to be able to create a global stylesheet for common classes/utility classes/custom properties and then a component-specific stylesheet. This would cut down on rework and duplication of styles for frameworks/component libraries. I would suggest reconsidering to be some sort of list structure.
@calebdwilliams Apple opposed a list. To alleviate we might introduce CSSStyleSheet object composition of sorts.
Styles that behaves like styles in a shadow root may not be the same as astyles that are global for a given class constructor.
I think we need something like user-agent styles for the custom elements, that bleed through shadow roots. To avoid problems, maybe it can be rule based, and applies only to the element (no selector, just a set of rules for all instances of some custom element).
It should be associated with constructors, because if scoped custom element registries are a thing, we don't want to complicate that (f.e. having to define same classes for every registry or for ever sub class).
It seems hacky to treat these like styles in a shadow root. Are they going to be multiple instances (less performance) per CE?
If they behave like user-agent styles, they only need to be defined once per class, and maybe subclasses can override them with matching static prop. HTML engine only needs to traverse the prototype once and use the style for all instances anywhere in the whole composed tree.
In my first sentence I meant, that shadow-root-like styles seem to be a lot different than useragent-like styles. Besides implementation difference, functionality isn't the same. It's easier to override useragent (-like) style than it is to override styles in a shadow root (specificity and open vs closed).
The OP suggested shadow-like style, but what point does shadow-root-like style have except for styling the host? In this case, I think implementing a way to define useragent-like style per class is cleaner both from usage and implementation point of view.
And plus, maybe a shadow-like style could be just an additional way to override the useragent-like style.
If someone wants to override the useragent-like style for many instances, they can, for example, make a subclass with new styles, and register a new element (f.e. in a scoped registry, and with the same name as before).
Maybe a shadow-like style feature would be useful for styling a few DOM element instances (sort of like attaching a shadow root to DOM elements ad hoc without defining any custom elements) without impacting the public API of the DOM elements (i.e. without modifying style= attributes that someone else may be in control of) but I would much rather have the useragent-like version first: it seems like a better way to define framework/library styling (as well as easy to override in applications).
I think I mostly agree with @trusktr here. @keanulee pointed out in a meeting the other day that this is the first feature to bind the Custom Elements and Shadow DOM specs together. After thinking about that comment, I don't think this feature is related to custom elements in general. Particularly, it seems strange that this needs to be an extra argument to customElements.define. If this was an API available on elements in general, then users could just use it in their custom element definition's constructor to achieve the same effect without causing these two pieces of the spec to become tightly coupled.
Consider if there were an Element.prototype.shadowStyles property, which was an (initially empty) array where you could put CSSStyleSheets:
class MyElement extends HTMLElement {
constructor() {
this.shadowStyles.push(new CSSStyleSheet(`:host{ border: 1px solid black; }`));
}
}
customElements.define('my-element', MyElement);
This way, you could use it on all elements, Custom Elements and Shadow DOM stay separate, and you could even potentially reorder them. [1]
If there's initial concern around this being an array, then maybe push could throw if the list size is already >=1 (i.e. maybe this needs to be an array-like "ElementShadowStyles" thing) and we could allow it not to throw after those concerns were handled?
It doesn't couple them together, it just uses the same list to store the style sheets, to avoid introducing more cascade complexity. (Note that anything else is a non-starter at which point this'll probably just not happen.)
I don't believe there's an observable difference between the shadow cascade order and UA stylesheets, so all those long comments predicated on such a difference seem incorrect. Unless someone can supply an example. (None of the spec writers or implementers at the face to face were able to.)
@annevk, the part I specifically mean to single out is that this feature doesn't seem related to custom elements, which makes customElements.define an unideal entry point. If we're only looking for a way to add styles to an element (acting as if they were in the element's shadow tree even if there is none etc.), requiring authors to create custom elements to get that ability seems unnecessary.
Acting as if they were in the shadow tree is the wrong way to think about this. The shadow tree has a style sheet feature that requires those style sheets to end up in the cascade somewhere. This just reuses that location.
The way I phrased this in the meeting is that you can think of this feature in one of two equivalent ways:
:host as applying to the custom element.So, if you are unclear, please refer to the first interpretation. Unless someone can satisfy my above request for an example of the difference, then these two interpretations are equivalent.
Stated another way, this feature is definitely a custom elements feature per (1), but because of the above equivalence, implementers can reuse existing machinery by thinking about it as (2). Web developers should continue thinking about it as (1) though; (2) is an implementation detail.
Hopefully you can forgive us for summarizing the feature with our implementer/spec-writer hats on in the initial report back from the face to face.
If (2) is a more accurate and generalizable description of the feature and is unrelated to custom elements, why is the feature only available to custom elements? It seems pretty clear how to separate the two since it sounds like that's the intended implementation anyway.
(2) is not a more accurate and generalizable description of the feature. (2) is an equivalent description of the feature. It is exactly as accurate and generalizable as (1).
Make-believe shadow roots, in particular, are not something we're interested in introducing, spreading, or generalizing. They are just an implementation detail.
Also, it doesn't make much sense to offer this for built-in elements, since those are already covered by the UA style sheet.
So in my understanding
class MyElement extends HTMLElement {
constructor() {
super();
}
}
// if constructable stylesheet is a thing
var css = new CSSStyleSheet(':host { color: blue; }');
customElements.define('my-element', MyElement);
var my_element = document.createElement('my-element', css);
document.body.appendChild(my_element);
Then color: blue is applied to every <my-element>, although at this moment you have no way to retrieve the CSSStyleSheet object from the element itself.
and then,
var root = my_element.appendShadow({mode: 'open'});
will make it appear in root.styleSheets(0), such that
root.styleSheets(0) === css
is true.
No, that wasn't what was discussed.
It's inside the options bag passed to customElements.define(). We don't modify createElement.
And if you attach a shadow root, it does not show up in shadowRoot.styleSheets. That's why we keep saying it's unrelated to shadow roots.
It is exactly as accurate and generalizable as (1).
I'm not sure I follow. Is the feature not expected to be implemented as (2)? Does the dependency on custom elements of description (1) not prevent it from being used by elements that are not customized?
Make-believe shadow roots, in particular, are not something we're interested in introducing, spreading, or generalizing. They are just an implementation detail.
It seems like if it's acceptable to create shadow roots on built in elements and that creating shadow roots solely for the purpose of adding styles is considered expensive enough to merit introducing the feature discussed here, then certainly it does not follow that the concerns resulting in this feature being proposed for use with custom elements do not apply equally to built in elements.
Some browsers may implement this as (2). Others may implement it as (1). Others may implement it a third way. It doesn't make any difference, observably. They are equivalent.
that creating shadow roots solely for the purpose of adding styles is considered expensive enough to merit introducing the feature discussed here
That's not the motivation. The motivation here is to offer feature parity with built-in elements to custom elements.
Then, if they're equivalent, wouldn't it be better to always implement as (2) and expose the feature to all elements? Otherwise, the performance problem it was created to mitigate is left unaddressed for built in elements, which seems like a loss given that the actual functionality provided by the feature is strictly redundant. Further, addressing this problem for built in elements later would result in API similar to if it had been designed for use with all elements in the first place (i.e. without any connection to a custom element definition), introducing a third way to do the same thing.
Do any implementors expect that (2) would result in worse performance for their implementation?
Sorry, I didn't see your comment before sending mine.
That's not the motivation. The motivation here is to offer feature parity with built-in elements to custom elements.
If this is only about providing feature parity and not about performance but (2) is equivalent, then isn't the feature purely redundant? Why include it in the spec?
(2) is not equivalent. I can create a custom element with some styling and let the user of that custom element attach a shadow root if they need to.
@domenic thanks for the clarification!
It's inside the options bag passed to customElements.define(). We don't modify createElement.
Ah, so it's better than you have to specify for each createElement() with correct combination of
element's name and corresponding stylesheet. Sounds nice.
And if you attach a shadow root, it does not show up in shadowRoot.styleSheets. That's why we keep
saying it's unrelated to shadow roots.
Okay, so it's fine not to expose the shadow root in the .styleSheets.
So what happens in the following case?
var css = new CSSStyleSheet(':host > span { color: blue; }');
customElements.define('my-element', MyElement, { stylesheet: css });
and
case 1
<my-element>
<span>Hello, World</span>
</my-element>
case 2
<my-element>
<#shadow-root>
<span>Hello, World</span>
</#shadow-root>
</my-element>
So only in case 2 you see blue "Hello, World"?
(In other words, can we have a selector to make case 1's Hello World blue?)
(Sorry, this may be what Polymer folks asked yesterday, but let me clarify...)
@TakayoshiKochi see https://github.com/w3c/webcomponents/issues/468#issuecomment-370672895, they should both be blue.
That makes sense, thanks for pointing out the difference.
(edit: referencing @annevk's comment from earlier)
@annevk okay, so :host interpretation at selector matching varies depending on whether it has a shadow root or not.
Then after the case 1 above,
case 1+
var myElement = document.querySelector('my-element');
var root = myElement.attachShadow({mode: 'open'});
root.innerHTML = `<slot></slot>`;
The <span>Hello, World</span> becomes not matching the selector.
@TakayoshiKochi the intent is that if you attach a shadow root the slot in the style sheet list would start pointing to it and taken into account as far as matching goes. I'm probably missing a subtlety though since I haven't thought the entire CSS side through.
The implication of the case 1+ above is that once you attach a shadow root, then :host > span starts to indicate the direct children of shadow root which is <span>.
The actual <span>Hello World</span> is still a child of shadow host, and even though it is slotted to appear as if it's a child of <my-element>, it's not an immediate child of the shadow root, therefore :host > span selector doesn't match.
I hope we are on the same page.
@TakayoshiKochi @annevk
No. I think there is misunderstanding there.
A style defined in a shadow tree never applies to host's descendants, except for ::slotted is used,
because host's children are NOT :host's children from CSS's perspective here.
In case of ":host > span", a selector matching should not traverse into the host's children in any case.
Instead, it should traverse into shadow root's children for ":host > span".
We never mix host's children and shadow root's children here.
":host > span" never matches the shadow host's children. It matches only the shadow root's children.
I believe this matches the Blink's behavior.
@HyatoIto that was my understanding when I wrote https://github.com/w3c/webcomponents/issues/468#issuecomment-371381991
But @annevk pointed out that the interpretation of :host changes whether the corresponding custom element has shadow root or not.
I missed the child selector. I guess I'm not sure what the model should be exactly then. It does seem useful if :host works for this style sheet even if there's no shadow root, not sure about matching descendants of the host though... And matching actual descendants might well be a use case so maybe we need something else?
Yeah, also I'm worried whether selectors without :host make any sense in the stylesheet added to a custom element or not. e.g.
div { color: green; }
can match
<my-element>
<div>Am I green?</div>
</my-element>
?
Since attachShadow has been mentioned various times, and since that does not really need a constructible ShadowRoot (illegal indeed), how about:
const style = element.attachStyle(optionalConfiguration);
We could have an element.shadowStyle or anything else that makes sense there, and the following benefits:
Thoughts ?
First off, the performance problem we're trying solve here is that a custom element with a custom style has to attach a shadow root and create a style element in every instance. I've never heard of concrete use cases which necessitates an API to apply a set of style rules to some builtin elements that are not met by inline style attributes or style elements without violating the encapsulation of shadow trees. Anyone proposing to add such an API should enumerate a list of concrete (not theoretical) use cases for which such an API is needed.
Second off, the reason modeling it as a style element inside a shadow tree came up was to avoid introducing a new cascading order in addition to the ones we added for shadow trees. Introducing a new cascading order is extremely expensive and we'd most certainly not support such a proposition. That's completely orthogonal to what kind of API is needed.
In case of ":host > span", a selector matching should not traverse into the host's children in any case.
Instead, it should traverse into shadow root's children for ":host > span".
I don't see anything like that in https://drafts.csswg.org/css-scoping/#host-selector. :host > span would never apply because those rules are never matched against the span. But the shadow root's children thing also doesn't make sense to me. Does Blink really look at the parent element crossing the shadow root for this?
(More to the point, does it do it for all descendant combinators?)
@annevk Do we need a way to give full UA-like style including descendants for this case?
E.g. Imagine <cool-list> and <cool-item>, both are quite dumb custom element that only needs styling. But specifically, <cool-list> wants to have style for <cool-item> when they are direct children under the element.
You want this UA-like stylesheet.
cool-list {
... (A)
}
cool-item {
... (B)
}
cool-list > cool-item {
... (C)
}
So (A) and (B) can be achieved by adding :host { ... (A) } and :host { ... (B) } to the custom element registry for each, the (C) is problematic in this case.
One pessimistic idea is just giving up for such case to provide UA-like shadow for custom elements, and ask authors write its shadow DOM and style accordingly, such as
<cool-list>
<#shadow-root>
<style>
:host > * { display: none; }
:host > cool-item { display: list-item; ... (C) }
</style>
<slot></slot>
</#shadow-root>
</cool-list>
@emilio CSS Scoping you quoted says:
The :host pseudo-class, when evaluated in the context of a shadow tree, matches the shadow tree’s shadow host. In any other context, it matches nothing.
means if :host > span is evaluated in the context of a shadow tree, :host matches its shadow host, and > means its immediate children (relationship), which traverses into its shadow tree because the context is the shadow tree, and then finds span.
I opened https://github.com/w3c/csswg-drafts/issues/2423 and https://github.com/w3c/csswg-drafts/issues/2424 about things that naively don't make sense to me.
means if :host > span is evaluated in the context of a shadow tree, :host matches its shadow host, and > means its immediate children (relationship), which traverses into its shadow tree because the context is the shadow tree, and then finds span.
I don't see how that paragraph redefines how child combinators work, honestly.
Selector matching is specified to work on the DOM. A child of the shadow root isn't a child of the shadow host.
I don't see how that paragraph redefines how child combinators work, honestly.
@TakayoshiKochi @emilio
I think what you might want to take a look are:
, instead of:
I think 3.2.1 and 3.2.2 explain the rationale and expected behavior well.
@hayatoito that explicitly says:
Note: Remember that the descendants of an element are based on the light tree children of the element, which does not include the shadow trees of the element.
Or am I missing something else?
I was missing something else:
"For the purpose of Selectors, a shadow host also appears in its shadow tree, with the contents of the shadow tree treated as its children. (In other words, the shadow host is treated as replacing the shadow root node.)
Also:
When considered within its own shadow trees, the shadow host is featureless. Only the :host, :host(), and :host-context() pseudo-classes are allowed to match it."
Note: Remember that the descendants of an element are based on the light tree children of the element, which does not include the shadow trees of the element.
Or am I missing something else?
I think this paragraph is just a reminder for a reader to explain a general rule about descendants in DOM, for the purpose of comparing a general rule with an exceptional rule which is being explained in that section from the Selector's perspective.
@TakayoshiKochi https://github.com/w3c/webcomponents/issues/468#issuecomment-371453775
E.g. Imagine
and , both are quite dumb custom element that only needs styling. But specifically, wants to have style for when they are direct children under the element.
Personally, I'd implement it as:
var listcss= new CSSStyleSheet(':host { ... (A)}');
customElements.define('cool-list', MyElement, { stylesheet: listcss});
var itemcss = new CSSStyleSheet(':host { ... (B)}, :host-context(cool-list) { ... (C)}');
customElements.define('cool-item', MyElement, { stylesheet: itemcss });
However, I'd say being able to do something like below, would be intuitive and usefull.
customElements.define('cool-list', MyElement, { stylesheet: new CSSStyleSheet(`
:scope { ... (A)},
:scope > cool-item { ... (C)},
:scope > :not(cool-item){ display:none; }
`);});
customElements.define('cool-list', MyElement, { stylesheet: new CSSStyleSheet(`
:scope { ... (A)},
:scope > cool-item { ... (C)},
:scope > :not(cool-item){ display:none; }
`);});
This shouldn't work, as per the current CSS scoping rule.
Host's descendant nodes are out of scope in any case, from the perspective of a style rule defined in a shadow tree.
To satisfy such a requirement, I think we have to introduce a new combinator or a new pseudo class, such as:
:host ======> coolitem { ... }
:host::child-in-real-tree(cool-item) { ...}
I don't think we want to introduce another primitives. :(
Just to be clear, I'm fine with :host and :host-context, as it solves the majority of use-case I'm aware of. I don't want to undermine the consensus.
That's just mental experiment to broaden supported use-cases.
Host's descendant nodes are out of scope in any case, from the perspective of a style rule defined in a shadow tree.
But if we would think of the use case of "Provide a lightweight mechanism to add styles to a custom element" - OP title, does not mention shadow root necessarily.
So if we would look from the perspective of Domenics 1.
It introduces a way to add your own user agent stylesheets for a custom element, at a new-ish cascade order that is basically the same as UA stylesheet order but lives after existing UA stylesheets, is scoped to only apply inside instances of this custom element, and interprets
:hostas applying to the custom element.
:scope could be the element, not it's "some imaginary shadow context"
It's interesting that you quote me there. For me when writing that, the most important part of the paragraph was "for a custom element", i.e., not for arbitrary descendants of a custom element, but just for the element itself.
I would strongly suggest not scope-creeping this issue as that often results in derailing existing consensus and causing this feature to not get implemented after all.
Yup, styling custom element's children is not the use case which we should address from the beginning here. Please file another issue if someone wants to discuss that further.
Let's focus on the original use case.
@rniwa regarding cascading order, did y'all consider having the same cascade order as UA rules? It would behave as if the rules were appended to the end of the UA stylesheet. That doesn't seem very expensive to me for implementations or for cognitive overhead for web developers?
@ojanvafai I don't think we fully considered all the options, since we didn't have CSS folks in the room. On the face of it that strikes me as a better architecture than the fake-shadow-root setup. The only downside is that you don't have a selector then that matches the custom element itself, but that could maybe be added in the future.
I am wondering how we can have a simpler approach to address the original use case.
I have a concern that "using a fake shadow root and :host pseudo class" is a kind of complex than necessary, from the implementor's perspective. It might be difficult to implement it as a lightweight mechanism. It's unclear to me.
How about having a simpler approach, like the following?
customElements.define will take optional uastyle style (uastyle is a tentative name. We should have a better name).const uastyle = new CSSStyleSheet('...');
customElements.define("cool-element", CoolElement, { uastyle: uastyle });
In a custom UA style, we allow only a compound selector. Rules which are using complex selector should be ignored.
In addition to that, we allow only universal selector. We don't allow any concrete type (tagname) selector.
The following rules are okay because they are compound selectors which don't contain a tagname selector.
* {
color: red
}
.foo {
color: blue
}
The rules match <cool-element> and <cool-element class=foo>, respectively.
The followings rules in custom UA style are simply ignored:
```css
div { # Ignore this because this is a tag name selector.
color: red
}
cool-element.foo { # Ignore this anyway.
color: blue
}
Ah, I was kindly informed that CSS Scoping already has Default Styles for Custom Elements section, which might support my proposal well. I proposed it without knowing this section.
The basic idea, such as "complex selectors must be treated as invalid", looks very similar by accident. That's good.
I really like the approach of UA-like styles, from custom element developer perspective it seems intuitive and simple.
I'm just afraid that following, may be problematic for Scoped Custom Element Registries #716
Windows gain a private slot [[defaultElementStylesMap]] which is a map of local names to stylesheets.
As we could have multiple definitions for the same tag name within the window. So we should rather have this [[defaultElementStylesMap]] scoped the same way element registries are.
How would it affect cascading order?
I think we can update the definition of [[defaultElementStylesMap]] so that it also considers a scope.
I expect that Scoped Custom Element Registries will file an issue for CSS Scoping, when it is ready.
How would it affect cascading order?
The relative ordering of different default custom UA styles doesn’t matter as it is not observable, I think.
So, what's interesting is that the UA stylesheet-based spec text and the virtual-shadow-root based spec text are almost exactly equivalent in their observable consequences. The difference is just whether you use * or :host to match the element.
At the face to face @rniwa had a strong preference for phrasing things in terms of virtual shadow root, so that is the resolution we recorded in the minutes there. But it sounds like a few other implementers prefer phrasing it in terms of the UA stylesheet. I don't think it really matters much. But indeed maybe the UA stylesheet phrasing would be easier to spec.
So unless we really care about :host vs. *, in the end I think we should write in the spec whatever is easier for the person doing spec work. For implementers, they can use either approach; they will not be observably different in a way that is observable by web-platform-tests. (Again, except for :host vs. *.)
The problem I have with UA stylesheet is that it would then cascade differently when an actual shadow root is attached on the element. Also !important rules set in UA stylesheets override !important rules in user stylesheets. It's highly undesirable for authors to be able to override !important rules in the user stylesheets.
Thanks. Given that the UA stylesheet-based approach is a new idea and we haven't discussed pros and cons about virtual-shadow-root vs the UA stylesheet-based yet, it is worth discussing.
I prefer the UA stylesheet-based approach which disallows complex selectors, in every aspects.
Rune also prefers the UA stylesheet-based approach, because it only allows compound selectors.
For implementers, they can use either approach; they will not be observably different in a way that is observable by web-platform-tests. (Again, except for :host vs. *.)
I agree there is no observable difference (except for :host vs ), however, I have a concern that virtual-shadow-root approach can have observable effects, if a shadow root is attached. Implementers have to be aware of this case, and take care of this *side effect anyway. That sounds an undesirable overhead to me.
Also !important rules set in UA stylesheets override !important rules in user stylesheets. It's highly undesirable for authors to be able to override !important rules in the user stylesheets.
Yup, I agree that this might be undesirable from a component user's perspective, however, that can be desiable from the component author's perspective. Actually, that matches built-in elements' UA style's behavior, which browser vendors provide. We have some !important UA rules which we don't want users to override. That are intentionally done.
I am a fan of any idea which makes custom elements closer to built-in elements as much as possible.
@hayatoito I think @rniwa meant undesirable from the end user's perspective. It would go very much against the nature of CSS if the end user were no longer in full control.
Yup, I meant end users by component users in my previous comment. I should have clarified it.
I would like to let component authors (component developers) to decide to whether to use !important or not.
!important.!important.!important, and the end users don't like it, the component will become unpopular, and the end users will look for a replacement. Let the market be healthy.That's not what I mean by an end user. In typical usage an end user is the one that uses the browser and has no control over what components a site uses.
Ah, I see. Thanks.
Should we take care of the end users (who is browsing a site, using the browser), in this context?
They might want to customize a site they are browsing by injecting CSS (via browser's extensions or something), however, I am not sure how much we should take care of such a demand from end users.
CSS has thus far taken them into consideration (the "user style sheets" mentioned before are for them).
(Also, https://www.w3.org/TR/html-design-principles/#priority-of-constituencies is somewhat applicable here.)
Ah, that makes sense. Thanks. I wrongly read "user style sheets" as "UA stylesheet". Sorry for the confusion.
BTW, it looks that CSS cascading doesn't mention anything about "user agent important rules".
https://drafts.csswg.org/css2/cascade.html#cascading-order
Hmm, but I believe that is:
in most browsers.
Assuming that, our remaining concern is:
Is it okay or not to give component developers a power of defining user agent important rules? It is too powerful, and the end users can't override it.
I've found the latest one: https://drafts.csswg.org/css-cascade-3/#cascading, that looks correct.
I think a very naive solution for our concern is:
I prefer 1.
I don't think it's okay to provide web page authors a mechanism to set a style user (not UA) stylesheet can't override. That would be a serious degradation for end users who need to use user (not UA) stylesheet to adjust the visual appearance of websites for accessibility purposes.
I agree that having these sheets override UA or User stylesheets is not an option. @rniwa I think if we modified https://drafts.csswg.org/css-scoping/#default-element-styles to ignore !important rules in these sheets, then that addresses this concern, right?
@ojanvafai but then components won't be able to set the style that cannot be overridden by author scripts / users of components. This is why I think we need to define it as the same cascading order as the style within a shadow tree.
Since this thread becomes too loooong, let me summarize here.
We have the following two proposals in this thread, basically:
I don't object to 1, I'm fine to have both, however, it looks 2 has a higher priority for custom elements to me. I'm feeling that 1 is too coupled with the concept of shadow tree. So, I would like to have simpler one at first for custom elements, which can be explained even if Shadow DOM is removed from Web Standards. :)
If we have a use case for 1, we might want to file a separate issue. I'm afraid that I hijacked this thread. :(
For 2, I think the remaining concern is which cascading order default custom style should use.
We would object to doing (2) if it involves either one of
!important.!important style rules that can't be overridden by author styles (i.e. users of components).@rniwa, Is the resolution on this issue to accept either an HTMLStyleElement or HTMLLinkeElement in the custom element's style config? Not really sure how to refer to that, so I'll say
interface CEConfig {
style?: HTMLStyleElement | HTMLLinkElement;
extends?: string
}
customElements.define: void = (tagName: string, constructor: HTMLElement, config: CEConfig) => { /*...*/ }
No, the proposal here is to still take StyleSheet object which is available on HTMLStyleElement and HTMLLinkElement. Initially we thought this feature isn't useful without constructible stylesheet but we realized during F2F in Tokyo that this is not the case because HTMLStyleElement and HTMLLinkElement already exposes StyleSheet object.
@rniwa I'm trying to imagine the syntax for accomplishing this because the HTMLStyleElement.sheet property is null until it has been connected:
const sheet = document.createElement('style');
sheet.innerHTML = `* { color: rebeccapurple; }`;
sheet.sheet; // null
document.body.appendChild(sheet); // now this is applied to document.styleSheets
sheet.sheet; // CSSStyleSheet from document.styleSheets[document.styleSheets.length - 1]
Do you have an example of what that would look like from an author's perspective?
Edit
So I guess my concern is that the CSSStyleSheet object isn't exposed on those objects until it is added to a StyleSheetList.
@calebdwilliams you'd probably have to create an <iframe> if you cannot have it apply to the current document.
@annevk That feels like a hack. Why would I need to default style my element if I weren't worried about styles bleeding? Is there any way the initial implementation could take the HTMLStyleElement and/or the HTMLLinkElement and apply them somehow to get the style sheet object? Or is there potential for a third disabled option to CSSStyleSheet that would signify that it is scoped somehow?
@calebdwilliams yes, it is. The eventual solution is that you can construct CSSStyleSheet directly. That's being developed in parallel.
- fake-shadow-tree approach, allowing arbitrary selectors, and treat the stylesheet as it were defined at the beginning of fake-shadow-tree. That was originally proposed.
- "Allowing only compound selector" approach, which can be explained without the concept of shadow tree. I proposed this.
Update from Google (on behalf of @hayatoito , @TakayoshiKochi, and @rakina), regarding both approaches.
Regarding Ryosuke's constraints:
- User stylesheet can't override the styles components set with or without !important.
- Not have the ability to define the default style for a component as well as the !important style rules that can't be overridden by author styles (i.e. users of components).
It looks there is no feasible solution which satisfies the constraints, however, we think that is because constraints have a kind of contradiction, as such:
In 2, users (end-users) are honored over component-authors
In 3, users (component-users ) are not honored over component-authors.
Given custom element itself appears in user's markup, custom element itself can be considered owned by components users too. If both (component-authors and component-users ) appy a style, component users should have the ability to override its default style.
This matches the Priority of Constituencies, I believe.
https://www.w3.org/TR/html-design-principles/#priority-of-constituencies
Note that this discussion doesn't apply for a style of an element in custom element's shadow tree.
The shadow tree should be basically owned by components authors, exclusively.
Give that, our experimenal plan for approach 2 is "Disallowing important declarations" in Custom UA style, as I explaiend in https://github.com/w3c/webcomponents/issues/468#issuecomment-378861530.
The remaining concern is that component users might override the style accidentally.
However, it's user's freedom and responsibility to avoid such a situation.
Users should have a full control of a style for all elements in their markup, basically.
Again, this claim doesn't apply for a style of elements in the custom element's shadow tree. The shadow tree should be hidden in any cases, basically, from selector matching perspective.
If component-authors don't like this behavior, in any case, there is always an option to use (fake) shadow root with a style, ":host !important". We can rescue them, but I think it is rare case.
Clarification:
We will prototype both approaches, try to optimize both (e.g. skip creating a shadow root for fake-shadow-root), then compare pros and cons of both approaches.
Update from Blink:
We've implemented the fake-shadow-root approach here, and the UA version here.
We've also compared the performance (more detailed doc about comparison is here).
Most helpful comment
I really like this idea too, though it might be worth having a custom element-specific stylesheet instead:
This way you can retain a reference and add to, remove or modify the css rules in a familiar manner later if required.