-
Notifications
You must be signed in to change notification settings - Fork 719
[css-cascade-6] Strong vs weak scoping proximity #6790
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
Comments
@chrishtr What question specifically did you want to put on the agenda? I think this isn't a question we should resolve one way or the other until we have plenty of feedback from the authoring perspective, and imho we should wait until cascade layers are usable so that the feedback on how scoping should work is in the context of already having layers. |
The question is "which behavior should we settle on", so we can actually start an experimental implementation.
That means delaying the entire scoping feature for a significant amount of time; half a year at minimum, probably 1-2 years realistically. I'm not sure what the connection is with layers, tho - both of the proposed placements for scope are underneath layers, either just above or just below specificity. Aside from the procedural matters, i think the proposed "scoped descendant" combinator has an obvious answer - it has to be weak (less than specificity) or else it'll be incredibly confusing. The example shows off the canonical use-case - "dueling selectors" where you have mutually nested containers with similar children, and want the rule with the closest indicated container to win in each case - and if scoping was stronger than specificity, it would mean that using the combinator like this would accidentally also block the ability for any other code to override those properties, which is absolutely an unintended and unwanted side effect. And I think it would be confusing if the two scoping mechanisms used different scoping positions, unless there was a very good reason for it (which I don't think there is). More generally, I still feel very strongly that the "weak scoping" placement (scoping is weaker than specificity, but stronger than order-of-appearance) is the correct place to insert it, for several reasons:
|
The potential overlap with layers is that strong proximity would require layers in order to override scope. Without layers, strong proximity is not a viable option. I tend to agree with @tabatkins arguments here – in part because I don't like that required use of layers – but I also think it's reasonable to build a prototype, and experiment with that before forcing a resolution here. I don't know how long exactly we want to put off the final decision, but I'm happy to leave it as an open question for now, if it doesn't block prototyping some of the options, and experimenting with the feature behind a flag. |
I propose we tentatively recommend weak scoping proximity. The Chrome team intends to prototype this API pretty soon, and then would choose that implementation approach. Once the prototype is done we can ask developers to try it out and provide feedback on this question. |
The CSS Working Group just discussed
The full IRC log of that discussion |
I would love to see an example of situation where this matters. I think we are reasoning too much in the abstract, it would be nice to see what this looks like when authored, and what makes the most sense. At first sight, I'm tempted to think that we probably shouldn't do anything about the scoping at all, and just assume declarations are in the source order and have a selector that is the concatenation of the selectors of the parent scopes and the selector of the current declaration. Is that the "weak" proposal? |
@FremyCompany No, that's just Nesting, I suppose. Doing that would remove most of the point of Scoping in the first place, leaving us with just lower-boundary protection. @fantasai's link indeed shows off some of the use-cases for having Scoping actually, you know, scope. ^_^ |
Ok, lower boundary protection was actually what I thought I needed, but reading the examples, I can imagine it sometimes makes sense to have some form of scope-root proximity influence, for example the following example: @scope (.light-scheme) { a { color: darkmagenta; } }
@scope (.dark-scheme) { a { color: plum; } }
Given I agree with Tab that weak scoping proximity would be better than strong scoping proximity, I guess everything is good! |
The problem I have with that approach is that it doesn't scale well. Say a project had around 20 themes and anybody could add a theme by making a pull request with a new One way around this that works only when the theme class appears at the end of the class list could be: @scope (.light-theme) to ([class$="-theme"]) { ... } but that feels like a hack at best. But maybe this is a problem that should be fixed on the selector side (maybe a class suffix/prefix selector?) instead of by the semantics of scoping. Either way, I think strong scoping proximity would be very inconsistent with the way CSS currently works: <style>
.red p { color: red; }
.blu p { color: blue; }
style>
<div class="blu">
<div class="red">
<p>Blue text, despite proximity to <code>.redcode> classp>
div>
div> As well as solve the same problem that lower scope boundaries already solve. |
I would likely do it by using a Chrome has a prototype for the In my mind, to be preferable, proximity has to be the important factor in comparing two unequal selectors. It's possible to invent cases where a weaker selector should override a more powerful one, and where they are also scoped - but that's often a problem to be solved either at the selector level, or with explicit layering. I haven't yet seen a case where scope proximity should be the deciding factor between otherwise properly-unequal selectors. |
I'm excited to hear Chrome already has a prototype and to actually see it in action in my browser 😁 |
After playing around with the Chrome prototype a bit, I'm beginning to agree the weak scoping proximity is the right call. My initial gut was the opposite: strong proximity seems to more accurately reflect what I have historically wanted in component-based design. But I think intentional use of the inner scoping bound is a good way to reproduce the effect of strong scoping, while still leaving the author with more options provided by weak scoping. In other words, if the author wants strong scoping, they can achieve it by providing all scoped components with an inner-bound container (or containers), thereby preventing outer scoped rules from ever selecting elements inside inner scopes. In my experience using component based web applications, it's uncommon for an outer component/scope to need to apply general styles on all inner components, so applying an inner bound to the scope would likely be the norm — but I like that weak scoping doesn't lock me into that and I can code it a different way when needed. And paired with layers, there are some fascinating approaches that could be used here. Again, it feels like it provides the most flexibility to the author while still meeting the requirements. |
I know that @bramus has also looked into this some, and might be able to comment in more detail – but I think he also came down on the side of weak proximity? It seems to me that there is a clear and growing consensus around weak scope, and there have been no counter-examples showing where a stronger proximity weight would be better. The abstract argument is that Various people have explored this, and we've consistently come to the same conclusions. But I'm not sure how to prove we've 'done enough' here. Can we either get a counter-example to discuss, or resolve on the direction that has vastly more support, so that this feature can move forward? I don't believe this abstract concern should be holding up the spec, when all the signals are pointing the same direction. |
Yes. The story so far has always been that combinators don’t influence specificity, and I think it should be kept that way. The "scoped descendant" combinator should have no influence on it.
A reason might be #8500 (comment). I think the outcome of that issue and this issue are tied together. |
So after trying to wrap my head around the interactions of specificity, layers and proximity, I think there might be a case for having some mechanism for having at least opt-in strong proximity. Correct me if I'm analysing this wrong, but let's consider using scopes for theming. A straight-forward way of implementing this would be to have a As there's often subtle differences in how something looks in dark vs. light mode, it is reasonable to assume that both will have specific overrides with more specificity than the base-case, say when a specific element looks good with a generic box-shadow in light mode, but needs a more individualised shadow in dark mode. In this basic example, the lower boundary is enough to prevent the more specific dark-mode shadow to mess with the element within a nested light theme, which only has a less specific "generic" style. What worries me is that I don't think real world applications will always have a clearly defined lower boundary. In the example of themes, two CSS authors may (without knowing of each other) decide on different ways to scope their themes, where one uses This could not be solved by lower boundaries, as neither author would know where to end their scope, nor could it be fixed by layers, as these themes could be nested again and again, and the innermost theme will be the one we want to take precedence. Without having used scoping in real-world projects yet, I haven't come across any specific example where this sort of situation might naturally happen without an easy workaround, but I'd be surprised if these cases wouldn't end up happen a lot once scopes start to be used. As for a solution, I'm not sure how I'd prefer to "opt in" to strong proximity. Adding an optional keyword to the scope @-rule seems like a last resort. My initial idea was, based on misremembering Maybe an analogue operator that applies strong proximity could be a way remedy this? The advantage of that would be, of course, that it could be added later on and thus wouldn't get in the way of an initial implementation of Just to illustrate this with some pseudo-CSSsomething like @scope (.theme-light) { p { text-shadow: could turn into something like @scope (.theme-light) { :scope !>> p { text-shadow: |
I'm not sure I understand the scenario where this might occur. If my dev team was working on theming our site/app, we'd establish a plan and stick with it—I don't really expect the CSS spec to render that meeting of minds unnecessary. Your scenario kind of sounds more like one where you might be pulling in dependencies created elsewhere, perhaps from an open source pattern library. In which case, I would expect the pattern library to clearly document how it deals with theming. The other approach would be web components & shadow DOM, and would thus provide very strong encapsulation anyway. |
Yeah, the problem with examples #6790 (comment) like and #8500 (comment) is that they rely on an assumption that we know (in the abstract) what the author intended. As @bramus points out in his follow-up comment:
In the mis-matched lower boundaries example, the intent is clear to a human eye - but not at all clear as a general rule. 'Strong' scope proximity may help solve the relation between those two light/dark scopes specifically, but there might be third or fourth scopes in play: such as components on the page. Are we certain that the 'closer' of We just can't solve these problems for authors in the abstract, and we shouldn't try to. Which one should 'win' is often situational, and authors will need to manage those situations intentionally. We can't magically 'get it right' in every single situation, including authoring-mistakes. All we can do is provide the tools for authors to resolve those questions when they come up. We should think of specificity, proximity, and layers as tools that authors can use to balance the cascade as they need. From that perspective:
What has leaned me towards option 1 is that authors are already very familiar with managing specificity - but I do think either could work. I just don't want to sit on the decision indefinitely. I don't believe we're going to get a lot more author input on a prototype than we have already. |
The CSS Working Group just discussed The full IRC log of that discussion |
I don't have a problem with introducing weak scoping into CSS. I think it's super useful and we should have it. But what I am uncomfortable with is:
So I'm not convinced that what we're doing here with @scope is the best way forward. |
FWIW, I generally anticipate my approach with scope to be: first, setting up layers along the lines of When the component has a "slot" to nest more components, I will make heavy use of donut scoping to prevent the styles from reaching into other components. This prevents one component from styling another unless I explicitly need it to. I've essentially opted-in to strong scoping behavior. At this point, it's rare that I write styles in two components that might target the same element. But when I do, I would want weak scoping: When an outer scope doesn't use donut scoping, and targets elements of an inner scope, specificity becomes my only means of controlling which selector takes precedence; with strong scoping, I have no way to give the outer scope precedence. If I try to define this control using nested layers instead, scope X styles will always override scope Y styles, and I can't make informed decisions on an element-by-element (or property-by-property) basis. Maybe I'm missing something, but I'm not sure nesting makes much of a difference to me, as I would nest very similarly from component to component. And I can always use an id or |
On a higher level, maybe the question is this: is |
What if If we go for weak now, this can always be added later. (The modifier could also be backported to |
I'd go for something like That way To be honest, I think that's been the general consensus for a while now. It might be time to just conclude that the default will be weak scoping and move on with getting this into the browsers. |
I don't think a mechanic to toggle weak vs. strong scoping is a good idea. Picking one and just sticking with it, is better in my opinion. Weak proximity makes the most sense to me. |
I don't think that's a good analogy at all. Strong scoping proximity is much more similar to Strong-proximity scopes would fill a very similar role, except they would allow categorically overriding rules only for certain parts of the DOM. The problem with Adding opt-in strong proximity scopes would have the same effect as adding This system would still be open to extension by simply adding new strong scopes that override styles locally. This could probably best be compared to shadow-dom in how it over-rides styles locally, except it would be independent from the application logic and purely a styling thing. But again, that could easily be something to add later, and weak proximity is a good default. Maybe opt-in strong scoping should get its own issue so the question of which is default can just be closed already. |
I have a hard time answering this question because I'm having trouble understanding how tooling can leverage this proposal in general. For example, a component-oriented framework might want to use this feature instead of hashed class names to scope a component. So if you have a component like this: <div class="one"><span class="inner">span>div> You might naively compile scoped CSS to: @scope (.one) {
.inner { color: darkmagenta; }
} But then if you have a second component that uses the same <div class="one"><input class="inner">div> Now you have mistakenly applied the first component's styles to this component. So you haven't gotten rid of the need for hashing. Am I misunderstanding something? |
This is not something that Having some uniqueness is required to use |
@romainmenke If this is the wrong thread to ask this question, where is the right one? I was asked to comment on this strong vs. weak question as a framework author but I do not know how to give feedback yet because I do not yet understand my above question. |
I didn't mean to imply that this isn't the right thread, it might be, but I don't have any opinions or say about that :)
That is correct.
A few examplesTools/frameworks can automatically add An author might write : img {
aspect-ration: 1;
} <img ...> And the framework would convert to : @scope (.component-34htjkhjg) {
img {
aspect-ration: 1;
}
} And if the framework has a clear "hook" for template slots it could also generate lower boundaries. img {
aspect-ration: 1;
} <img ...>
<div class="template-slot-a">
div> And the framework would convert to : @scope (.component-34htjkhjg) to (.template-slot-a > *) {
img {
aspect-ration: 1;
}
} |
@matthewp The point is that once you can clearly select those two, you can guarantee that styles only apply within the outer scope but not within the inner scope. The second prat is what's more significant here, as the former could more or less be achieved by prefixing all your style rules with the selector for your outer boundary. |
Thank you @DarkWiiPlayer and @romainmenke for confirming my thoughts here. Given that, it's unclear to me, what problems is this meant to solve? I think I understand how it works better but not yet what problems it aims to solve. Is there a list of goals/non-goals somewhere? In my mind, if you still need to add unique selectors (aka hashes) to elements, then why not add them to all elements in the component? That's how it's done today. Implementing this for 1 element is no easier than implementing it for N elements. So maybe there's another use-case here that I'm missing? For example, the spec mentions overlapping scopes. That's a different use-case than the one I was thinking of. The nested dark/light modes. Is that the primary goal of this proposal? |
I'm not a framework maintainer myself and generally want this more for the benefits of clearer hand-written CSS; but I imagine preventing styles from leaking into nested components would be the main benefit here. Unless there is already some easy mechanism to achieve this in auto-generated CSS that I'm not aware of. As mentioned above, the ability to add lower boundaries is really the primary innovation in terms of what you can actually do, as opposed to upper boundaries which mostly overlap with simple CSS nesting. |
@matthewp I think the key here is @scope makes this possible now without the framework. I don't think it gives anything new to frameworks, but rather accomplishes something natively that developers have relied on frameworks/CSS-in-JS libs for. If the author gives each component a unique name/classname, they can manage scoping with native CSS and no ugly BEM class name patterns. With appropriate scoping, developers can style Controlling the lower scope boundary is an important part of this. I don't know that a framework can automagically manage that for the developer—but I think that's okay as it gives a lot of flexibility to developers as they will learn to use the feature. |
Thank you @DarkWiiPlayer and @keithjgrant if this proposal is targeting hand-written CSS then it starts to make more sense to me. @keithjgrant with your analogy to BEM, this proposal gets rid of the need for the Element and Modifier, but not for the Block, if I'm understanding correctly. Which means you still might use tooling to produce a unique Block name. Or you might use naming conventions to ensure there are no conflicting block names. |
The proposal isn't targetting hand-written CSS exclusively, but that is one area where it will be useful. As for the BEM situation, that's basically it, yes. You can axiomatically select elements with a "from to" rule without having to mark every child element with the corresponding parent-component, as long as you can formulate those two selectors properly. Whether this results in reduced complexity of framework code will depend on how much extra logic you need for all those child elements, which would be interesting question for us non-framework-authors, as we can't judge that. I imagine this could also be beneficial for the increased interoperability with non-framework code moving elements in and out of components, but that might not be a priority of most frameworks anyway. As a framework user, I only remember one particular case when fiddling around with svelte where some lines of CSS that would only apply after some slightly hacky logic changing things around, which were just getting thrown out by the framework, presumably because it relied on prefixing all selectors and had no way of doing that correctly. That's sadly too long ago to give a more detailed description of my problem at the time or to really tell if I was really just using the framework in a dumb way or not, but I do think that the simpler CSS that could be generated with |
I want to drop one of the common cases that I encountered for scoping — mixing user-generated content with custom components for editorial copy. We could want to target any semantic elements coming from markdown or whatever like My first naive idea to do this (following how we approximate this in our design system) could be something like @scope (.content) to (:scope [class]:not(.scope-element)) {
h1 {}
p {}
li {}
} (I'm using the This would apply the styles for any class-less elements, stopping the scope at any component that has a class, unless that component has a special Scope would guarantee that we won't have any collisions between our regular elements and any semantic elements used for custom components. For the strong vs weak — I don't have an opinion based on just theory, I would need to play with both variants, applying them to some practical code in order to understand the real consequences of this. On the first glance, I would say that having the |
@kizu Lower boundary 'scope end' selectors only match descendants of the I've written up a bit of a FAQ explainer-addendum covering some of the questions that have come up repeatedly around the scope feature, including this one. Hopefully that's useful to people who haven't been 'in the weeds' for the entire process leading here. (I also updated the associated explainer to match the current state of the (also updated) Working Draft specification. |
The CSS Working Group just discussed
The full IRC log of that discussion |
Issue 8 here is about strong vs weak.
This issue is to track resolving on one of the two behaviors.
The text was updated successfully, but these errors were encountered: