New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Early design review of light-DOM CSS Scope proposal #593
Comments
Hi there, Here are some of my initial thoughts. I would really like to know how this ties into other CSS scoping methods like shadow DOM and the proposed WICG/webcomponents#909 And would especially like to hear the feedback from @rniwa and @hober I think that having multiple CSS scoping methods that aren't build on the same principles or underlying primitives is problematic, and we should attempt to avoid that. There are real problems with shadow DOM scoping today, but solving those outside of shadow DOM would not be ideal. For instance, I have heard the need for scoped light DOM styling, ie. a more powerful ::slotted(), which seems related to this. |
Thanks for the quick feedback @kenchris! I agree that it's confusing to have both models use the language of "scope", but I don't think it means we can conflate them. While there are some similarities, these are two distinct problems – both with valid use-cases, but requiring distinct solutions. I've been happy to see that recent Cascade drafts refer to "encapsulation context" rather than "scope" for Shadow-DOM isolation. I'm also open to changing any of the language in this proposal - though I think this is the more common use of "scope" that authors are familiar with in the existing tools.
My proposal here is that getting shadow-DOM shouldn't solve both use-cases, but should instead free us up to solve the remaining presentation/selector-targeting use-case as a distinct problem-set. I hope that's a helpful clarification. Thanks again for your thoughts! |
I'm very happy to see a proposal that aims to address the problems that cause patterns like BEM or CSS Modules to proliferate. So, to decouple the different parts of this:
Some lower level comments below.
This is a bit ambiguous. I can see two possible meanings:
If you meant 2, that seems pretty uncontroversial. But in case you meant 1: I think this would break author assumptions about shadow DOM. Also, it was decided early on that we don't want explicit access to styling shadow DOM structure that has not been explicitly exported. Furthermore, my understanding of implementations is that this kind of selector matching across Shadow DOM boundaries would be very difficult to implement.
It is unclear whether the parentheses are optional or mandatory. In the examples that follow the part I quoted, there are no parentheses. Is that a typo or does it intentionally demonstrate that no parentheses are required in certain cases? Parentheses should definitely be required to disambiguate complex selectors, otherwise you don't know if
I definitely see use cases for selector lists as lower boundaries, e.g. blocks with multiple different areas to exclude. It is definitely useful to be able to vary a component's style based on its context. However, simply allowing a part of the selector to match outside the scope could lead to unintentional effects, when the author has not anticipated this. Consider this: <div class="b">
<div class="a">
<div class="c"></div>
</div>
</div> @scope (.b) {
.a .c { /* matches! */ }
} Now consider this HTML, with the same CSS: <div class="a">
<div class="b">
<div class="c"></div>
</div>
</div> The rule still matches, but the author may have not intended this, which breaks the isolation.
Could you elaborate on this? I don't understand it as written.
Until this point, I was under the impression that part of the reason for having an at-rule instead of a "donut" selector was the different cascade mechanism. If it just resolves to the same specificity as nesting, then it could have scope leaks. Consider this: #main a { color: red }
@scope (.my-component) {
a { color: purple; }
} <main id="main">
<div class="my-component">
<a>This is red?!</a>
</div>
</main> It may also be interesting to explore what a JS API for fetching "donut scope" elements could look like. I've often needed this, and ended up querying and filtering using
This gives me pause as well. It would be great if these issues can be solved with existing primitives. Though if there are widespread problems the existing primitives cannot reasonably address, solving the problems is more important than keeping the set of primitives constant.
My understanding is that this solves different problems than Shadow DOM. There are many use cases for partial style encapsulation where that is the only kind of encapsulation needed, and Shadow DOM would be too heavyweight a solution. Also, this being entirely a CSS side solution means it can address use cases where existing HTML is being styled. |
@mirisuzanne, while reading the (existing) :scope pseudo-class, it seemed to me that the idea is that there's an implicit |
Thank you all for the thoughtful notes! I'll work on some clarifications and updates to the proposal based on your feedback. In the meantime, here are some brief answers to the questions you raised:
@hober That's right. I'll add some clarity around it. I've also had some conversations with @tabatkins about using aspects of the nesting syntax here, like the @LeaVerou a few of the quick clarifications/answers:
I've done some work on this, but struggled to find a selector approach that makes sense to me. I'll try and document my thoughts on that a bit more, but if you have ideas, I'm interested.
Yes, this is what I meant. I agree that scope should respect existing shadow-boundary rules.
Yes, this was a typo. I agree that parentheses need to be mandatory.
I agree for lower boundaries. The question in my mind is if we also need (or should support) selector-lists for the scope root.
That makes sense, thanks. I've updated the explainer to require
That's a quote from the 2014 scope proposal, in which scopes act much like origins – with the order of priority determined by proximity (inner/outer), but that order is reversed for important styles. For example: @scope (.outer) {
p {
font-style: normal;
color: green !important;
}
@scope (.inner) {
p {
font-style: italic;
color: red !important;
} <div class="outer">
<div class="inner">
<p>
This paragraph would be
italic (inner-scope has priority for normal declarations), and
green (outer-scope has priority for important declarations)
</div>
</div> Shadow-DOM has similar importance-affected cascade rules, but in the reverse order.
Good question, I'll look into this more - depending where we land on selector-syntax… The biggest question is about where scope belongs in the cascade. I recognize that my approach is a major departure from previous W3C approaches (including both the 2014 The primary use-case that I'm trying to address is one in which component-styles are "locked-in" to avoid cross-contamination, but global styles are used to "tie it all together" with consistent patterns like typography and branding. The desired behavior is to prevent scoped styles from leaking out, without getting in the way of global patterns that should flow through easily. If we give scope proximity more weight than specificity, authors are left with very few tools to manage that relationship. By putting proximity below specificity, authors can manage it in several ways:
This in-but-not-out approach also matches the existing JS tools & CSS naming conventions that authors already use. Those tools add lower-boundaries, and a single attribute-selector of increased specificity – very easy to override from the global scope. I think this low-weight approach to scope is also backed up by…
Anecdotally, I hear many CSS beginners surprised that the fallback for specificity is source-order rather than proximity. This proposal would allow authors to opt-into that expected proximity-over-source-order fallback behavior. Meanwhile, Shadow-DOM already provides the alternative approach for more isolated "widgets" – where "encapsulation context" is weighted higher than specificity, and prevents style leaks in both directions. I think that can be expanded to make shadow-DOM declarative, and encapsulation available in the light DOM. This proposal would continue to be distinct, and cover a significantly different set of use-cases. |
Shadow DOMFor selectors in @scope rules in shadow trees, we should figure out which restrictions apply wrt matching elements outside the shadow tree. @scope rules in shadow trees should not be able to target elements outside the shadow tree, but what about :host/:host-context?
Pseudo ElementsIIUC, you can target the scope-root itself in Is this allowed? @scope (div::before) {
& { content: "xxx" }
} |
If you use
A few raw thoughts on this: Essentially the scope targeted by 1 <div class="a">
2 <div class="b">
3 <div class="c">
4 <div class="a">
5 <div class="b">
6 <div class="c"> It would be the elements in lines 1, 2, 4, 5. At first I thought a combinator makes more sense, but since the rightmost operand needs to match the selector target, that wouldn't work for specifying the lower boundary. Aside: What does |
@LeaVerou yeah, I've been working on this today, and came to a similar conclusion.
I put together a codepen example as I was working. It's slightly different from your example, in that I do match the lower-boundary itself, but not descendants of the lower boundary. That's the proposed pattern, used by scoping tools today. You can also see some JS there. Given the ability to reference the root The :donut(.a / .c):donut(.a / .x) {
/* establish both .c and .x as lower boundaries */
} That could also describe nested
In my current proposal, lower boundary selectors would only match descendants of the scope, so: <s>
<p>This is matched as part of the outer scope</p>
<s> <!-- lower-boundary of outer scope, and root of inner scope -->
<p>This is not part of the outer scope, but it does match as part of the inner scope</p>
</s>
</s> (I suppose that means it would match the same as |
I don't quite understand how to read the pen, but one of the questions in it is whether we can desugar to
That can only be applied to a single element, so in the common case where you have multiple scope roots, you still have to iterate over them.
Not a huge fan of the repetition of the scope root. No reason to disallow selector lists as the lower boundary (or even both), there is precedent of pseudo-classes accepting selector lists.
I think you perhaps misunderstood my question. |
Your clarifications/comments (on most of these) are the same conclusions I came to, so we're on the same page here, even if I'm not communicating it clearly. 👍
Makes sense. 👍
I understood that, but figured I could demonstrate it with a type as well as with a class or anything else. I had intended lower-boundaries to be descendants of the scope, never the scope itself. So I would expect @scope (.scope-media) to ([class|='scope']) {
/* automatically end this scope when we encounter another */
} If we wanted to allow single-element scopes, I would rather we use something like |
Hi there, Thanks for considering our feedback and the great discussion so far. We briefly discussed it in a call today and it would be great if you could ping us when you have something new to share! Thanks! |
Hi @mirisuzanne, @cynthia and I looked at this during a breakout in our Gethen VF2F today. We were wondering if you had any updates for us? Have you further explored integrating the new selection logic that |
@LeaVerou I have not been able to find a reasonable way to handle this with existing selectors. Establishing the lower boundaries can be done very roughly with
There is currently no way to establish a scoping element in CSS. One proposal was to use the nesting syntax for that - but after some investigation, and conversations with @tabatkins, I don't think that's possible. The nesting syntax was explicitly designed to de-sugar into individual However, I do think your proposal for a new donut selector is interesting. I included that in my (still exploratory) Editor's Draft. With something like that, the selection logic of an /* these would select the same elements, without lower bounds */
@scope (.scope) { .target { … } }
.target:in(.scope) { … }
.scope.target, .scope .target { … }
/* these would select the same elements, with lower bounds */
@scope (.scope) to (.lower, .bounds) { .target { … } }
.target:in(.scope / .lower, .bounds) { … }
/* not possible with existing selectors */ Both |
Hi @mirisuzanne, As TAG, we are happy to see that making the selection logic a selector is an avenue that has been sufficiently explored, even if it doesn't resolve in actual edits to the specification. We will leave the details of designing this feature to the CSS WG and we are going to go ahead and close this. Thank you for flying TAG! |
HIQaH! QaH! TAG!
I'm requesting early TAG review of my proposal for light-DOM CSS Scope.
Previous scope proposals have attempted to address highly-isolated use-cases along side the more light-touch issues of namespacing and selector-proximity. Those attempts were largely abandoned in favor of Shadow-DOM solutions -- which strongly prioritize strong isolation. In the meantime, third-party tools (eg CSS Modules) and conventions (eg BEM) are more often used by authors to provide low-isolation scoping for light-DOM components. I'm proposing a native CSS approach to those use-cases.
Further details:
We'd prefer the TAG provide feedback as 💬 leave review feedback as a comment in this issue and @-notify @mirisuzanne and @lilles
The text was updated successfully, but these errors were encountered: