construct-stylesheets
construct-stylesheets copied to clipboard
Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after?
From https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets:
The user agent must include all style sheets in the DocumentOrShadowRoot's adopted stylesheets inside its document or shadow root CSS style sheets.
These adopted stylesheets are ordered after all the other style sheets (i.e. those derived from styleSheets).
Is there a particular reason that style sheets in adoptedStyleSheets are ordered after style sheets from <style>s in the associated tree? AFAICT, adoptedStyleSheets is being designed primarily as a mechanism for sharing style sheets amongst many elements, but it feels strange that shared styles would take precedence over styles that are certainly only applicable to a particular instance (i.e. <style> in the associated tree).
p.s. Yes, you could just add your instance-specific style sheets to the end of the instance's adoptedStyleSheets instead of inserting them into the tree, but why is that necessary?
They have to be put somewhere, and putting them before all the stylesheets coming from link/etc seems odder than putting them after.
Sounds like this is a subjective issue, so here's my preferred color for the bike shed:
I conceptually think of the style sheets in a single root as if they were concatenated into a single style sheet since they pretty much work this way. Adding a style sheet to adoptedStyleSheets seems akin to adding @import statements to the concatenated style sheet. When importing a style sheet with @import, I'd usually put it at the top so that my inline rulesets will override anything that I'm getting from the import.
So, if we use this example:
const sheet1 = new CSSStyleSheet();
sheet1.replaceSync(".foo { color: red; }");
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.adoptedStyleSheets = [sheet1];
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
It feels more like it should be:
@import 'data:text/css,.foo { color: red; }';
.foo { color: blue; }
than:
.foo { color: blue; }
@import 'data:text/css,.foo { color: red; }';
I think this sentiment becomes even more applicable if CSS Modules become a thing. But I totally recognize that it's not critical since I could just stop adding inline <style> elements and switch everything over to using adoptedStyleSheets to get the same effect.
I see where you're coming from within shadow roots, but consider the issue on the outer page, where .styleSheets will usually only include stylesheets coming from <link> in the <head>. It seems a lot weirder (to me, at least) for the JS-created adopted stylesheet to default to being earlier in the cascade than the <link rel=stylesheet>.
If we could somehow put it in the middle, where everything in the head came first, then adopted stylesheets, then style in body, I think that would be the best solution. But all markup-based stylesheets are bodged together into .styleSheets, so we instead have to choose to put the adopted sheets before or after all of them. :/
Putting JS-managed stuff after HTML-managed stuff is the general pattern for this sort of thing, so that's what we went with.
(Really, what we want is the ability to tag the adopted stylesheet as part of a specific Cascade Origin, so you could set things up as being "user-agent" level, and thus automatically overridden by anything at author-level, such as style contents. But that's not currently possible; perhaps in a v2?)
What if adoptedStyleSheets was moved from DocumentOrShadowRoot to HTMLStyleElement? Instead of attaching style sheet objects to a single bucket that applies to the entire root and requires making the 'before vs. after' decision, you would attach them to a specific node instead and inherently be able to decide the placement yourself.
Main document:
const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
// If you want adopted styles after everything else you've imported:
document.head.appendChild(externalStyles);
In a ShadowRoot:
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
// If you want adopted styles before everything in the root:
shadowRoot.insertBefore(externalStyles, shadowRoot.firstChild);
You could put them in multiple places if that's what you needed:
<style id="alwaysLoses"></style>
<style>.foo { color: red; }</style>
<style id="alwaysWins"></style>
<script>
document.getElementById('alwaysLoses').adoptedStyleSheets = [a, b];
document.getElementById('alwaysWins').adoptedStyleSheets = [c, d];
</script>
edit: Removed an incorrect line in the second example.
If you're going to give them before/after locations in the DOM tree, they should just be DOM nodes. For example, HTMLStyleElement.
If you're going to give them before/after locations in the DOM tree, they should just be DOM nodes. For example, HTMLStyleElement.
CSSStyleSheet could have a method that produces a new HTMLStyleElement associated with the style sheet. Using createLinkedStyleElement as the name, then this example (that I updated to use prepend)
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
shadowRoot.prepend(externalStyles);
would become
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.adoptedStyleSheets = [sheet1];
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
shadowRoot.prepend(...[a, b, c].map(sheet => sheet.createLinkedStyleElement()));
Is that kind of what you were thinking?
Yes, although I'm not sure there's too much value in indirecting through CSSStyleSheet instead of just creating a HTMLStyleElement directly.
I was thinking that the node created by createLinkedStyleSheet would continue to be associated with the CSSStyleSheet rather than containing a copy of the style sheet at the time they were created - replace, etc. would 'update' all of the associated nodes.
I was thinking even simpler: just use HTMLStyleElement only, don't ever use a CSSStyleSheet apart from htmlEl.sheet.
I'm not sure I follow - could you give an example?
document.adoptedStylesheet will be used most of the time by web component authors to provide the default styles of components, leaving the users the ability to customizable and override the styles using link or inlined <style>. This is pretty much broken, when they are applied after.
In fact, I thought it behaved like user agent's stylesheets.
While this problem is more important in the document case, it's too for shadow-dom, where developers should be able to add a
I think something like document.adoptedStylesheetBefore (or similar) could also solve the problem, and in fact be the API that most developers use, given the use case of Constructable Stylesheets today.
Yeah, the reasoning in this thread seems fairly convincing. Like I said in my earlier comment, I think the current placement (page, then adopted) makes the most sense for the outermost page, but within a shadow root the reasoning is different.
(Specifically, style in shadow roots seem more similar to style in body, which in my earlier comment I admit would probably make sense to place after the adopted sheets.)
So I support some way of having adopted sheets go before the inline sheets. Whether that's by shifting where they go in general, or by adding a secondary attribute that places them in a different spot, I don't have a real opinion on.
@rakina, @domenic ?
Sounds reasonable to have overridable styles. What if instead of changing/adding another type of adoptedStyleSheets, we make the positioning up to each stylesheet?
So you would have a new option overridable or something in the CSSStyleSheet constructor, where if it's true we would put it before all the other styles, and if false/unset we put it after.
(though all of them are overridable by the inline style attribute so I guess we need a better name/term).
I don't have any strong preference though, probably having two kinds of adoptedStyleSheets will be less confusing?
cc @mfreed7 @chrishtr
I don't have strong feelings, but am confused by this use case. Why would a web component ever modify a shared document-level resource? Put them in the shadowRoot.adoptedStyleSheets. Then they will behave similar to UA styles.
So you would have a new option overridable or something in the CSSStyleSheet constructor, where if it's true we would put it before all the other styles, and if false/unset we put it after. (though all of them are overridable by the inline style attribute so I guess we need a better name/term).
Also no personal preference, maybe something like order (analogous to css order) can cover this and other use cases better.
I don't have strong feelings, but am confused by this use case. Why would a web component ever modify a shared document-level resource? Put them in the shadowRoot.adoptedStyleSheets. Then they will behave similar to UA styles.
Ideally all components would use shadow-dom, but in practice it’s not possible. Believe me, we have tried really hard. The lack of :host-context(), style any descendant (slotted only styles direct children), integration with form (I know it will get better), developers might want some flexibility.
In addition apps (not isolated components) components usually work better without shadow-dom, otherwise all pages content is inside the shadow-dom. For SEO reasons is still useful to build web components without shadow-dom enabled.
In any case, for anything that is more complex than a simple web component, there are many good reasons to avoid it.
In stencil, developers can use native shadow-dom (recommended), scoped (simulated shadow-dom using normal css) and normal.
In both scoped and normal, and polyfilling native shadow-dom, the stylesheet is attached to the document.
However, in the shadow-dom use case I would also expect a inlined
Thanks for clarifying!
The lack of :host-context(), style any descendant (slotted only styles direct children), integration with form (I know it will get better)
I worry that at least two of these are working around unimplemented features in some browsers. Adding new features to the spec will not help with that, as those browsers will not implement the new features either.
Could you expand on the "style any descendant" problem, and how using document-level stylesheets solves it?
So you would have a new option
overridableor something in theCSSStyleSheetconstructor, where if it's true we would put it before all the other styles, and if false/unset we put it after. (though all of them are overridable by the inlinestyleattribute so I guess we need a better name/term).
Why would a web component ever modify a shared document-level resource?
I also think modifying or picking a single behavior for a shared style sheet is kind of strange and would be a problem because that chosen behavior would apply to all users of that style sheet. For example, if a package distributes modules exporting CSSStyleSheets, then that package has to decide the single behavior that all of its users use. One workaround would be to make the package export a function that generates a CSSStyleSheet with the desired options, but that starts cutting into the value of sharing them because you need a copy for each distinct set of options requested.
I worry that at least two of these are working around unimplemented features in some browsers. Adding new features to the spec will not help with that, as those browsers will not implement the new features either.
I guess that's fine, we support Constructable Stylesheets because they actually help the performance for most of our users (Chrome and Android). We fallback to the old slower behaiour in other browsers. The problem would be to not even use Constructable Stylesheets in Chrome because it does not cover our use case.
Could you expand on the "style any descendant" problem, and how using document-level stylesheets solves it?
So withing shadow-dom, ::slotted() is the only way to style direct children, it's not possible as long as i know to style any descendant h1 for example, in "normal" CSS it's as easy as doing:
my-cmp h1 {}
but in shadow-dom, ::slotted(h1) will not target a nested h1 element.
The CSS Working Group just discussed Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after?, and agreed to the following:
RESOLUTION: constructed style sheets to always go after
The full IRC log of that discussion
<stantonm> topic: Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after?<astearns> github: https://github.com/WICG/construct-stylesheets/issues/93
<stantonm> github: https://github.com/WICG/construct-stylesheets/issues/93
<stantonm> heycam: spec says orderering of stylesheets should be ?, but actually should it be other way around?
<heycam> https://github.com/WICG/construct-stylesheets/issues/93#issuecomment-487772869
<bkardell_> q+
<stantonm> TabAtkins: my comment says make sense to put after
<stantonm> emilio: don't think there's a strong reason for one or other
<stantonm> hober: agree, but is there consistency arguments?
<stantonm> TabAtkins: maybe related to @font-face, which is after
<astearns> ack bkardell_
<stantonm> bkardell_: talking about shadow root and document styles, strangely related - some things bleed through, some blocked
<stantonm> ... not sure I get what ordering means
<stantonm> ... would like them to come before
<stantonm> ... different use cases, adopt styles from outside
<stantonm> TabAtkins: all sheets that come from markup come before adopted style sheets
<stantonm> bkardell_: we want to use these for UA equivilent, seems like not the right move
<stantonm> TabAtkins: adopted style sheets help when people put things directly in shadow dom
<stantonm> ... component usage will move to adopted style sheets, gives full control
<stantonm> ... if you use style inline, it's baked into the template
<stantonm> ... similar to link style sheet in head, where you override with adopted
<stantonm> ... both ordering can make sense, no strong argument
<stantonm> bkardell_: we don't know which is correct
<stantonm> hober: if we don't know, ask the author
<stantonm> ... implies two sets of adopted style sheets, seems complex
<stantonm> ... not sure if additional complexity is worth it
<tantek> present++
<stantonm> TabAtkins: don't need two sets, just move from shadow root to adopted
<stantonm> bkardell_: can you do adopted style sheets outside shadow dom
<stantonm> TabAtkins: yes
<stantonm> hober: summarizing = after, if they want before have to specify
<stantonm> RESOLUTION: constructed style sheets to always go after
<TabAtkins> astearns: And if we realize that authors do ahve common need to put the adopted ones first, we're free to add a knob for that.
I'm coming late here (apologies, I've been living under a rock for the past year).
Couldn't the adoptedStyleSheets be placed in the cascade as if they were part of style elements in the the root's ::before and ::after pseudo-elements? (...or, for the document, part of the body's ::before and ::after... Alternatively, give the body its own adoptedStyleSheets set)?
That would give authors some flexibility regarding their overidability, and well defined, non-arbitrary semantics.
As an author, being able to put styles between the head and the body makes a lot of sense from an overridability standpoint.
You could also make them visible (but read-only, or even opaque) in the .styleSheets list though that's probably a different issue.
Adding some more thoughts: while the after position makes sense for app writers, the before slot would seem a natural fit for library authors.
Another vote for having these appear before style tags (or, alternatively, giving client code control over the ordering). My use case is a library that injects styles but expects users to occasionally want to override those with regular old style sheets.