Visitar URL original
[css-cascade-6] Strong vs weak scoping proximity · Issue #6790 · w3c/csswg-drafts · GitHub
Skip to content

[css-cascade-6] Strong vs weak scoping proximity #6790

Closed

Description

@chrishtr

Issue 8 here is about strong vs weak.

This issue is to track resolving on one of the two behaviors.

Activity

  1. fantasai commented on Nov 3, 2021

    @fantasai
    Collaborator

    @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.

  2. tabatkins commented on Nov 3, 2021

    @tabatkins
    Member

    The question is "which behavior should we settle on", so we can actually start an experimental implementation.

    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.

    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:

    • One of the core complaints that people have had with specificity thruout CSS's history has been specificity wars - you style an element, then need to override it in some circumstances which needs a more specific selector, then need to override that in some circumstances which requires an even more specific selector. If your circumstances fall into the "general tag behavior, then class-specific, then element-specific", specificity works for you; if it doesn't (if this is just levels of classes with different semantic importance in your codebase), specificity fights you. In particular, this means that you shouldn't reach for an ID selector at first, for instance, even if it would be the most convenient way to target a particular element, unless you purposely intend for these styles to be very strong and hard to override. This has resulted in multiple CSS management strategies all basically concluding "don't ever use ID selectors", because once you do you're locked into using them in every subsequent rule targeting that element.

      If scoping was above specificity, this would be all of that but worse. The scope would make the styles more powerful than any global rule. Just like how using a single ID selector to target an element means you have to always use an ID selector to target that element, using scoping to style an element means you'd have to use scoping to always style the element, and you don't even have the flexibility of using a dummy ID on body or writing an ID selector twice; you must target the same scoping element, or something closer. And worse - if it's the same scoping element, then you still have to specificity-fight; if it's closer, then you've just done the equivalent of using two ID selectors, and the next one-off override has to scope even tighter.

    • That's a holistic argument. A more specific one is - I can't think of any justification whatsoever why @scope (.foo) { .bar {...}} should win over #bar, when targetting the same element. ID selectors are by their nature selecting one specific element to put styles on, which is why a single one automatically wins over any other selectors in any quantity. The additional constraint of a scoping element doesn't make a general selector (like .bar) more specific (in the general sense, not the CSS term-of-art sense) than a selector explicitly targeting one single element specifically (like #bar).

      But if scoping is weaker than IDs, it's either weaker than all specificity, or it's inserted into the middle of the specificity tuple. I think the latter has far more confusion potential, especially when considered in light of the specificity-altering pseudo-classes (which presumably would not be able to alter the scope of a selector!).

    • The potential use-cases for scoping cover a broad range of things, from "I want to write baseline styles, then have all context-specific styles automatically win without worrying about specificity on either side" (already covered, better, by layers), to "I want to write generic styles for this container, but have a particular instance work differently" (already covered by existing specificity), to "I have clashing styles, but want the styles that are 'closest' to the element to work, regardless of how things get nested" (the canonical scoping use-case). We should let scoping do what only it can do, and leave the other problems to the other solutions, rather than trying to interweave them; mixing solutions can easily produce author confusion.

    • The other part of scoping is the lower-boundary protection, to prevent your container styles from "leaking out" further. This is only tangentially connected to any notion of style strength within the container, so authors can easily want one without the other. If scoping is relatively strong, tho, then they must take it into account when considering whether to use this functionality for lower-boundaries as well; if it's relatively weak, then they can usually ignore it and consider the two halves separately. In particular, with the strength set to "just higher than order-of-appearance", they can pretty much completely ignore it if they want; since order-of-appearance is almost always just a meaningless tiebreaker rather than a semantic decision, aka random noise that they'll use specificity to fix if necessary, anything else operating at approximately that level is random noise as well if they're not paying attention to it, and the same fixes apply.

      If scoping operates at any higher point, then we should really disconnect it from lower-boundaries, to avoid confusion when people just want one of them. But that would be fairly sad, and imo itself somewhat confusing, since they operate in such similar semantic space.

  3. mirisuzanne commented on Nov 4, 2021

    @mirisuzanne
    Contributor

    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.

  4. chrishtr commented on Nov 4, 2021

    @chrishtr
    ContributorAuthor

    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.

  5. css-meeting-bot commented on Nov 10, 2021

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-cascade-6] Strong vs weak scoping proximity, and agreed to the following:

    • RESOLVED: WG leans towards weak proximity at this time, and recommends this direction for prototyping to get more feedback
    The full IRC log of that discussion <fantasai> Topic: [css-cascade-6] Strong vs weak scoping proximity
    <fantasai> TabAtkins: Right now, scoping spec cascade-6 is intentionally ambiguous on exactly where scoping sits in cascade
    <fantasai> TabAtkins: offers two options: less strong that specificity (just stronger than order of appearance) and another that's stronger that specificity
    <fantasai> TabAtkins: Given it's currently ambiguous, makes difficult to do test implementation
    <fantasai> TabAtkins: Would like to do a test implementation, and prefer weak scoping
    <fantasai> TabAtkins: I've argued in the thread about why the weaker scoping is the better way to go, going for strong would be a mistake imho and make things less usable for authors
    <fantasai> TabAtkins: but for the moment, I think we should at least currently resolve to go with weak scoping
    <fantasai> TabAtkins: and revisit later
    <fantasai> TabAtkins: fantasai said in issue, she believes that related features have been released to make a decision
    <Rossen_> q
    <fantasai> TabAtkins: that would delay scoping feature by a year or two
    <fantasai> TabAtkins: and I don't think the input we'd get would be worth that level of delay
    <Rossen_> ack fantasai
    <drott> fantasai: no problem if chrome wants to go ahead and do prototyping of weak scoping
    <drott> fantasi: exactly how it works will be fundamental to how css is used
    <drott> fantasai: need to be diligent and figuring it out
    <drott> fantasai: 6 months timeframe is reasonable for that
    <TabAtkins> (I really, *strongly* think that going with "strong" scoping would be making a serious mess, but I argued that in volume in the thread already.)
    <drott> fantasai: scoping feature is desired and useful
    <Rossen_> +1 to fantasai point ^
    <fremy> Is this discussed in any GitHub issue?
    <drott> fantasai: more important to get it right, first time around - waiting 6months to a year is reasonable for the proprotion of this feature
    <TabAtkins> fremy: Yes, in the github issue linked in the agenda and right here, a few lines up
    <drott> fantasai: i suggest to resolve with sth like: "the WG is leaning towards weak proximity and thinks it's the right way for prototyping"
    <fantasai> fantasai: but keep the issue open for discussion
    <TabAtkins> (oh, it *hasn't* been linked)
    <miriam> +1
    <fantasai> github: https://github.com//issues/6790
    <fantasai> RESOLVED: WG leans towards weak proximity at this time, and recommends this direction for prototyping to get more feedback
    <fremy> It hadn't been linked indeed; this is why I was confused about it ^_^
  6. FremyCompany commented on Nov 10, 2021

    @FremyCompany
    Contributor

    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?

  7. fantasai commented on Nov 10, 2021

    @fantasai
    Collaborator
  8. tabatkins commented on Nov 11, 2021

    @tabatkins
    Member

    @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. ^_^

  9. FremyCompany commented on Nov 15, 2021

    @FremyCompany
    Contributor

    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; } }
    Even though, I think there are probably better ways to achieve that...

    For example:

    @scope (.light-theme) to (.dark-theme) { ... }
    @scope (.dark-theme) to (.light-theme) { ... }

    Or maybe even:

    .light-scheme { --link-color: darkmagenta; }
    .dark-scheme { --link-color: plum; }
    a { color: var(--link-color); }

    Given I agree with Tab that weak scoping proximity would be better than strong scoping proximity, I guess everything is good!

  10. DarkWiiPlayer commented on Feb 21, 2022

    @DarkWiiPlayer

    For example:

    @scope (.light-theme) to (.dark-theme) { ... }
    @scope (.dark-theme) to (.light-theme) { ... }

    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 .css file (an extreme example, I know); then every theme would need a list of all other themes in their lower boundaries and adding a new theme would require adding it to every other theme too.

    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>.red</code> class</p>
       </div>
    </div>

    As well as solve the same problem that lower scope boundaries already solve.

  11. mirisuzanne commented on Sep 7, 2022

    @mirisuzanne
    Contributor

    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.

    I would likely do it by using a data-theme attribute, and then scoping ([data-theme=light]) to ([data-theme]).


    Chrome has a prototype for the @scope rules using 'weak proximity' behind the 'Experimental Web Platform features' flag (navigate to chrome://flags and search). In my experimentation so far, I agree with the majority of responses here that 'weak proximity' is the best way to go. At this point, it would be helpful if anyone arguing for 'strong proximity' could provide examples where it really is preferable.

    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.

  12. DarkWiiPlayer commented on Sep 7, 2022

    @DarkWiiPlayer

    I'm excited to hear Chrome already has a prototype and to actually see it in action in my browser 😁

  13. 24 remaining items

  14. matthewp commented on Mar 16, 2023

    @matthewp

    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?

  15. DarkWiiPlayer commented on Mar 16, 2023

    @DarkWiiPlayer

    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.

  16. keithjgrant commented on Mar 16, 2023

    @keithjgrant
    Contributor

    @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 .card .title differently than .callout .title and not worry about the two .title selectors conflicting.

    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.

  17. matthewp commented on Mar 16, 2023

    @matthewp

    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.

  18. DarkWiiPlayer commented on Mar 16, 2023

    @DarkWiiPlayer

    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 @scope would have allowed for that to work.

  19. kizu commented on Mar 16, 2023

    @kizu
    Member

    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 h1, p, li etc, but then we need to be able to stop applying those selectors when they're inside our components.

    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 :scope for the limit, as I'm not exactly sure if without it the scope would start and end on itself, due to the .content matching the [class]?)

    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-element, in which case it would participate in the scope.

    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 weak as default would be safer, but it would be interesting to play around with the strong one as well.

  20. mirisuzanne commented on Mar 21, 2023

    @mirisuzanne
    Contributor

    @kizu Lower boundary 'scope end' selectors only match descendants of the :scope by default - so your example would be valid, but the :scope ancestor would not be required for the use-case.

    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.

  21. css-meeting-bot commented on Mar 22, 2023

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-cascade-6] Strong vs weak scoping proximity, and agreed to the following:

    • RESOLVED: cascade proximity is weaker than specificity
    The full IRC log of that discussion <emeyer> miriam: With @scope, there are two features that help avoid large scoped things overriding smaller scoped things
    <emeyer> …ONe of those is pretty strong, which is the ability to set lower boundaries
    <emeyer> …The other is somewhat weaker, which is a heuristic priority for inner over outer
    <emeyer> …We’re calling that cascade proximity; question is whether that’s more or less powerful that the specificity heuristic
    <emeyer> …Proposal is to have it be stronger than, because we’re trying to reduce specificity reliance
    <emeyer> …When these two heuristics conflict, specificity is easier to change
    <emeyer> s/stronger than/weaker than/
    <emeyer> astearns: I suggested people provide arguments in favor of stronger, but all the comments in the issue argue for weaker
    <emeyer> astearns: Comments?
    <bramus> SGTM
    <emeyer> RESOLVED: cascade proximity is weaker than specificity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions