Skip to content
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

CSS @scope #472

Closed
lilles opened this issue Jan 11, 2021 · 48 comments
Closed

CSS @scope #472

lilles opened this issue Jan 11, 2021 · 48 comments
Labels
position: positive venue: W3C Specifications in W3C Working Groups

Comments

@lilles
Copy link

lilles commented Jan 11, 2021

Request for Mozilla Position on an Emerging Web Specification

Other information

@annevk
Copy link
Contributor

annevk commented Jan 12, 2021

cc @heycam

@annevk annevk added the venue: W3C Specifications in W3C Working Groups label Jan 12, 2021
@emilio
Copy link
Collaborator

emilio commented Jan 19, 2021

I think my biggest concern here is performance, tbf. Depending on how this interacts with the cascade you need to either:

  • Collect "applicable scopes" by walking up all the ancestor chain of the element you're styling, or...
  • Selector-match all the selectors in @scope rules normally and filter them out afterwards if they're affected by one or multiple @scope rules.

I guess at least we can use the bloom filter for scopes that don't directly match the element.

I think with the explainer's definition you basically need to do the later (plus you need to find the "scope depth", somehow).

How does this interact with nesting? The basic @scope syntax seems basically an ancestor combinator to me, unless I'm missing something (I'm assuming @scope doesn't do weird stuff with Shadow DOM).

@andruud
Copy link

andruud commented Jan 19, 2021

@emilio Honest question: if you do the latter, do you expect performance to be any worse than a descendant combinator?

The basic @scope syntax seems basically an ancestor combinator to me

True, but crucially it addresses this problem though: https://github.com/oddbird/css-sandbox/blob/main/src/scope/explainer.md#the-nearest-ancestor-proximity-problem

@emilio
Copy link
Collaborator

emilio commented Jan 19, 2021

Probably not for simple scopes. For more complex scopes it can get slower if implemented naively at least, but probably not too terrible...

I've found the size of some data structures in our style engine (in particular the ones needed to do the specificity / source order sorting) really performance-critical, but that's a bullet we'll have to byte at some point I guess.

To be clear didn't want to sound discouraging of the feature, I was just thinking out loud :-)

I think the feature is generally good. It's a bit unfortunate that it overlaps a lot with css-nesting IMO.

It's also a bit weird that the specificity of the selectors used in the scope rule isn't considered at all. I wonder if that would confuse authors more than it helps... There's a lot of discussion in the explainer of what interaction does this have with specificity / the cascade, so I guess this is not all set on stone... I wonder what's supposed to happen with the "proximity" when you start nesting scope rules? Is that something you're supposed to be allowed to do or useful? (honest question, it might not be...)

@mirisuzanne
Copy link

Thanks, this is helpful.

I agree that there's some strange overlap with nesting at the moment – I'm looking into how we might either combine these efforts in some way, or differentiate them better. That may also have some impact on how we think about specificity. I initially had the scoped-selector add to specificity, but worried it might add confusion for scope to have two impacts on the cascade. I'm still very open to that direction.

I need to think a bit more about the use-cases & implications of nested scope. My immediate reaction is that people might want it in terms of more narrowly targeting selectors (not sure how useful that would be), but only the innermost scope would be relevant to cascade proximity.

@chrishtr
Copy link

chrishtr commented Jan 9, 2023

I think this is a duplicate of #625.

@zcorpan zcorpan mentioned this issue Jan 10, 2023
@emilio
Copy link
Collaborator

emilio commented Jan 18, 2023

@mirisuzanne / @lilles / @andruud: Did the weak vs. strong proximity argument reach a conclusion in the working group? Seems rather important to sort out those issues...

@lilles
Copy link
Author

lilles commented Jan 18, 2023

No indication of a conclusion in issue 6790.

@mirisuzanne
Copy link

It has not been resolved.

@mirisuzanne
Copy link

I'll try to get a resolution on the weak/strong issue, since the consensus so far is pretty clearly in favor of weak scoping. On the question of scope vs nesting, there is some overlap. Primarily: some use-cases for nesting could be better expressed as a scope. But that's the extent of the overlap.

  • Nesting is a syntax sugar for writing any multi-part selectors, without additional semantics about why or how the parts are related. Since there are no clear semantics implied by nesting syntax, there is also no cascade behavior specific to nesting.
  • Scope is a much more narrow feature that gives powerful element-tree-scoping semantics and related cascade influence to a limited set of use-cases. Some of them can be expressed as nesting, but would lack the associated semantics of the relationship. Others are impossible without 'lower boundaries' of some kind. Scopes are always individual tree fragments of the DOM, with optional 'lower boundaries', and a defined ancestor/descendant relationship.

The similarities have come up in multiple CSSWG issues, and I think we've done a good job exploring how they interact (& acts like :scope in some situations) – but always with a clear conclusion that they are distinct features.

@keithjgrant
Copy link

Issue 6790 has been resolved in favor of weak scoping

@mirisuzanne
Copy link

mirisuzanne commented Apr 24, 2023

@emilio All the major issues have been resolved here, including strong v weak scoping, and how to handle nested rules/specificity.

There have been many discussions about the 'overlap' with nesting, and we've consistently found that scope is a much more narrow and specific use-case than nesting, and nesting doesn't solve any of the issues specific to that use-case. They feel related, since @scope implies a nested relationship in the DOM – but @container also implies that relationship, and we understand it is still a distinct feature.

At this point, the spec is feeling pretty stable, and I'd love to get a more solid position here – or new issues that we can address more clearly.

@SebastianZ
Copy link
Contributor

SebastianZ commented Apr 28, 2023

For what it's worth, I've just created https://bugzilla.mozilla.org/show_bug.cgi?id=1830512, as it seems there wasn't a bug yet to track this feature.

Sebastian

@keithjgrant
Copy link

keithjgrant commented Jun 5, 2023

Is there any chance of an update here? As a developer, I really want this functionality—and I’ve seen a lot of similar discourse in the community. Chrome seems to have it slated for "enabled by default" release in v117 v118: https://chromestatus.com/roadmap

@collimarco
Copy link

I would love to see this implemented (I would definitely use it in Rails together with ViewComponents)

@emilio
Copy link
Collaborator

emilio commented Jun 9, 2023

So, I don't think we're necessarily opposed to this, but personally it seems like there's enough overlapping with nesting that waiting to see how people use nesting to see if this extra complexity is worth it might be wise?

I get that with nesting you can't do the lower boundary or proximity-dependent specificity things, but in general the use cases I've seen for that don't seem too convincing to me? The color-scheme examples could be better addressed by machinery the browser already has (see w3c/csswg-drafts#7561 for example). Other examples feel like just re-inventing the shadow DOM style encapsulation but without shadow DOM, in a way that has negative performance implications...

@keithjgrant
Copy link

I get that with nesting you can't do the lower boundary or proximity-dependent specificity things, but in general the use cases I've seen for that don't seem too convincing to me? The color-scheme examples could be better addressed by machinery the browser already has (see w3c/csswg-drafts#7561 for example). Other examples feel like just re-inventing the shadow DOM style encapsulation but without shadow DOM, in a way that has negative performance implications...

As a developer focused on web applications, I couldn't disagree more. The runaway prevalence of CSS-in-JS libraries like CSS Modules, Styled Components, and Emotion shows just how badly we need this sort of encapsulation. Shadow DOM is neat, but far too isolated for most app-like use cases, IMO. @scope provides brilliant fine-grained control over exactly which styled do and do not "leak" into child components, something not easily accomplished with Shadow DOM, and certainly not with nesting.

(FWIW, Webkit has also indicated support at WebKit/standards-positions#13)

@emilio
Copy link
Collaborator

emilio commented Jun 9, 2023

Shadow DOM is neat, but far too isolated for most app-like use cases, IMO.

Curious, can you elaborate? I'm also interested into use cases for leaking some styles but not others.

@scope provides brilliant fine-grained control over exactly which styled do and do not "leak" into child components, something not easily accomplished with Shadow DOM.

Sure, I get that, but it does so at a non-trivial performance cost. Shadow DOM can statically determine which rules potentially apply to any element in its subtree, which is not true for @scope.

@keithjgrant
Copy link

keithjgrant commented Jun 9, 2023

Curious, can you elaborate? I'm also interested into use cases for leaking some styles but not others.

Trivial example: Say my page has a series of frequently used utility classes, such as text-center to apply text-align center. I can’t add that class to elements inside the Shadow DOM without also duplicating that CSS inside the Shadow DOM’s styles.

I suppose you could take effort to define "common" styles like this and ensure that stylesheet is @import-ed into every Shadow DOM as well as into the Light DOM, but there’s a fair bit of overhead to separating common styles from "normal" page styles that are meant for the Light DOM. During real-world development, I imagine this would frequently involve moving chunks of CSS between the two stylesheets as needed, then having to worry about its impact on other Shadow DOM components.

I also imagine there could be scenarios where you want some subset of styles to be available in components A, B, and C but not in components X and Y... though I’d have to think through that further to identify specific use cases.

Sure, I get that, but it does so at a non-trivial performance cost. Shadow DOM can statically determine which rules potentially apply to any element in its subtree, which is not true for @scope.

I think that's a fair concern. I haven’t seen this brought up yet by the Chrome or Webkit teams, so I’m curious what their approach is in this regard

@kizu
Copy link

kizu commented Jun 9, 2023

I was planning on writing an article about the @scope use-cases (mostly waiting for a Chrome Canary bug to be fixed with the @scope and @layer combination in order for me to create the demos I want), but, in short, some use-cases:

  • Editorial styles. Typography, headers, paragraphs, lists, for user-generated content, but with an ability to arbitrary insert components with styles we do not want to touch (achievable via the donut scope).

    In cases like this we could actually sometimes want not just donut scoping, but, I dunno, onion scoping? We would have a typography wrapper that assigns the styles from its root to the slot, but also provide an “inner” slot, which could be used by any inserted components, allowing the styles inside those inner scopes to get the same styles as our main one.

  • Any theming. It not just covers color-scheme, but, really, anything — selecting one of 3-4 themes, sizes, variations.

    Usually, this is done by providing a number of CSS variables on the closest ancestor — but for more complicated cases this can be overcomplicated when we have to basically define anything we want to be overridable as an exposed variable.

    Scopes would allow to handle this in one of two ways: a) using donut scope — we could just stop the theming styles at the start of the next scope, b) via proximity — if we're ok with the styles mixing, we'd want the definition that is closer to take precedence. Those theming scopes could be intersecting or exclusive, and would want to assign the same properties, so having both proximity and donut scoping would be really helpful there.

Shadow DOM handles some of it, but — we don't really have an exclusively declarative shadow DOM, it does not solve all the issues, brings its own, harder to adopt to projects that do not use it, and so on. It is just a different tool, solving similar issues, but with its own pros and cons.

As a developer, I would really, really want to see @scope in CSS, and I'll try to gather more use-cases, as well as practical demos with the current implementation in Chrome Canary. Not sure about the ETA, as I had some other articles planned, but we'll see :)

@slorber
Copy link

slorber commented Jun 29, 2023

I'm the framework lead of Docusaurus (docs React SSG from Meta) and we'd really like to use @scope.

We know that Shadow DOM exists, but the truth is that it's much harder to understand how it works in depth, figure out if it would even work fine with SSR/SSG, teach it to our users, and figure out how to integrate it nicely into the framework.

@scope is much easier, and when something is easier, people simply adopt it.

More context here: facebook/docusaurus#6032

I don't have the skills to figure out if it's a good decision to add @scope to CSS, but I can only share this "human behavior" fact:

  • we haven't adopted ShadowDOM
  • we would have adopted @scope in no time if it was available in all browsers

@mirisuzanne
Copy link

mirisuzanne commented Jun 29, 2023

There are several key differences between the design of Shadow DOM context and Scope that make sense when you step back from the general description of 'scoped styles' and instead look at the use-cases they are meant to address:

Shadow DOM was designed for 'custom elements', a way of extending HTML. Much like normal HTML elements, custom elements can have built-in behavior, and default styles. Shadow Context is designed to work in much the same way as UA styles - a default is provided by the element at a low cascade priority, and then the outer page is allowed to override the defaults with higher-priority page styles. The styles are always specific to one element, the boundaries are defined by the DOM, and the page takes precedence.

That's an excellent design for 'custom elements' - but it's the opposite of what developers want or expect from 'components' in a design system.

Design system 'components' are not always tied to a single HTML element or entirely consistent DOM structure. In fact, authors often try to detangle components from underlying html semantics/structure by using explicit class names, and avoiding specific nesting. This is a central tenet of OOCSS from 2008ish, and has become best practice baked into most CSS conventions. Meanwhile, components are designed as part of a system. While they may expose certain properties for contextual customization, the general desire is that component styles are higher priority than the surrounding page defaults. Not just in the sense of specificity, but in the sense of proximity. Components are not separate from the page styles, a default to override, they are part of the page styling as a whole. Scope is meant to prioritize proximity and avoid name conflicts as part of the system.

Sometimes there will be overlap. Sometimes an element is also a low-level component - and components will often use custom elements internally. In those cases, these features will continue to work as expected: the shadow context provides element-specific defaults, while the component pattern provides specifics styles based on the page/system.

I believe we've made a major type error by treating 'scoped styles' broadly in both cases as a single problem that can be solved by a single feature. These are fundamentally different problems, and as long as we keep insisting that 'Shadow DOM solves scope', we're going to keep having authors frustrated and surprised about how it works.

Both use-cases are important, both are useful, and - even though they have conceptual overlap - both are fundamentally different needs.

@chrisdotcode
Copy link

chrisdotcode commented Jul 1, 2023

Another thing I didn't see mentioned explicitly as of yet is that @scoped CSS will work without JavaScript, unlike the Shadow DOM.

This is not only a fantastic win for accessibility and writing less code (and therefore less bugs), but it also makes entirely-isolated, single-file components possible in pure HTML- with no build step - for the first time ever:

components/card.html:

<style>
@scope (.card) {
  p {
    color: blue;
  }
}
</style>

<div class="card">
  <p>Hello, world!</p>
</div>

N.B. that you can't replicate the above with CSS Nesting either, because you don't have style encapsulation with nesting. Up until now, you could encapsulate your JavaScript, and your files encapsulated your HTML, but non-leaky CSS encapsulation was never truly possible.

@dutchcelt
Copy link

dutchcelt commented Jul 3, 2023

As someone who primarily creates Design System libraries, tools and workflows using Web Components, I keep running into an issue with Web Components that limits their utility in Design Systems. Scope resolves this limitation.

I use Shadow DOM and slots in almost all the Web Components I've created. In other words, I've explicitly decided to incorporate the Light DOM. As search engines improve and Web Component accessibility improves, I may, at some point, be able to do away with the Light DOM.

<article-component>
	<template shadowrootmode="closed">
		<link rel="stylesheet href="article.css"/>**
		<! - - GENERATED CONTENT - - >
	</template>
</article-component>

Soon, maybe, but not today.

So, I rely on slots, and they're great. I love the flexibility they bring. And from a component perspective, the Light DOM provides the (documents) intention, and the Shadow DOM encapsulates the component mobility and robustness.

However, Design Systems tend to be holistic and are not concerned about light and shadow. And they shouldn't be. You need to be able to style all of it consistently and reliably. Styling Light DOM content ported into a Shadow DOM via the Shadow DOM is limited to the point you have to style it via the Document when the design requires an exception. So, you end up with two style sheets, one for each DOM. The reliance on styling slotted Light DOM in these use cases makes Web Components cumbersome.

article-component {
	& p {
		color: white;
		background-color: navy;
	}
}

Using a plain CSS selector like this is incredibly fragile and unacceptable. No way would I allow this to go to production. So we need to scope it with a build tool or dynamically construct a stylesheet from the web component itself.

article-component[data-v-698c154c] {
	& p[data-v-698c154c] {
		...
	}
}

I can't use something like BEM or Tailwind because then I'd introduce another layer of complexity I was trying to avoid in the first place. The better option would be to drop Web Components altogether.

The best solution I've seen so far is @scope.

@scope (article-component) {
	& p {
		...
	}
}

This would be a usable way for a Design Systems Web Component to add Light DOM styles to the Document.

So, ironically, the best thing ever to happen to Web Components encapsulation is the ability to scope the Light DOM.
I really hope this feature lands in Web Browsers.

@emilio
Copy link
Collaborator

emilio commented Jul 3, 2023

@dutchcelt Can you provide a more concrete example? I'm confused about:

Using a plain CSS selector like this is incredibly fragile and unacceptable. No way would I allow this to go to production. So we need to scope it with a build tool or dynamically construct a stylesheet from the web component itself.

I think I'm missing what is incredibly fragile about it. Is it specificity / cascade priority? Because afaict that's the only effective difference betweeen @scope (article-component) { & p {} } and article-component p {}?

This would be a usable way for a Design Systems Web Component to add Light DOM styles to the Document.

How, exactly? Is the idea that the component would add a global stylesheet to the document? To all its ancestor shadow trees? How does that work with nested components?

@emilio
Copy link
Collaborator

emilio commented Jul 3, 2023

I was on PTO, so sorry for the lag replying. Some replies / questions in non-specific order (sorry for the mega-reply too):

@slorber:
We know that Shadow DOM exists, but the truth is that it's much harder to understand how it works in depth, figure out if it would even work fine with SSR/SSG, teach it to our users, and figure out how to integrate it nicely into the framework.

@scope is much easier, and when something is easier, people simply adopt it.

Sure, that's fair. Though adding another feature to solve the same problem is generally adding complexity, rather than removing it, IMO. @scope is rather subtle in how it interacts with the cascade too, even though it's simple to use. Hopefully the way it's designed means that it does mostly what people want and people don't need to understand why the rules end up in the order they end up...

@mirisuzanne:
Design system 'components' are not always tied to a single HTML element or entirely consistent DOM structure. In fact, authors often try to detangle components from underlying html semantics/structure by using explicit class names, and avoiding specific nesting. This is a central tenet of OOCSS from 2008ish, and has become best practice baked into most CSS conventions. Meanwhile, components are designed as part of a system. While they may expose certain properties for contextual customization, the general desire is that component styles are higher priority than the surrounding page defaults. Not just in the sense of specificity, but in the sense of proximity. Components are not separate from the page styles, a default to override, they are part of the page styling as a whole. Scope is meant to prioritize proximity and avoid name conflicts as part of the system.

I see. Again, asking from ignorance: which kind of design-system-component thingie couldn't be made into a web component? I guess I need to read up into how people use design systems now but a lot of the design system work I'm familiar with in the Firefox front-end (here) is based on web components and the need for @scope hasn't (to my knowledge) come up (yet?). I guess that for the kind of use case you're mentioning you'd have a <div class="button-group"> (or some other tag) instead of a <moz-button-group> web component for example, and in that case you can't really use shadow dom to scope the styles or what not?

I believe we've made a major type error by treating 'scoped styles' broadly in both cases as a single problem that can be solved by a single feature. These are fundamentally different problems, and as long as we keep insisting that 'Shadow DOM solves scope', we're going to keep having authors frustrated and surprised about how it works.

I mean, shadow dom does provide scoping capabilities, even though perhaps not in the way everybody likes. I agree @scope is much more powerful in principle, but I'm a bit concerned about providing different ways of doing the same thing, where the simpler thing (@scope + light dom) is just slower (potentially substantially slower) than using Shadow DOM.

@chrisdotcode
Another thing I didn't see mentioned explicitly as of yet is that @Scoped CSS will work without JavaScript, unlike the Shadow DOM.

This is not true, see declarative shadow dom (whatwg/dom#831 / #335).

N.B. that you can't replicate the above with CSS Nesting either, because you don't have style encapsulation with nesting. Up until now, you could encapsulate your JavaScript, and your files encapsulated your HTML, but non-leaky CSS encapsulation was never truly possible.

Thanks for providing a concrete example! What part of that very small card example is not doable with nesting? I understand the rules will have different cascade priority. But don't @scope (.card) { p {} } / .card { p {} } / .card p {} match exactly the same elements? If not, what am I missing? Again, it might be that I'm missing something trivial here that makes the feature work much differently than I thought?

Thanks.

@dutchcelt
Copy link

dutchcelt commented Jul 3, 2023

@emilio Yes, I can. My apologies for some assumptions about the design implementation on my part.

I think I'm missing what is incredibly fragile about it.

Plain CSS selectors like this have very low specificity. These can/will be unintentionally overridden. The only way around that is class names or unique (data) attributes. Assuming that's even possible. A Web Component, in most of my cases, from a Design System perspective, needs to work in different CMSs and frameworks with their own build tooling.

Note that I'm not trying to have separate styling for slotted content; I usually only need to add a style to it. And Scope helps in that regard.

How, exactly? Is the idea that the component would add a global stylesheet to the document?

The approach I'm currently taking is adding styles from the Web Component itself by adding the light DOM stylesheet as an adoptedStyleSheet.

I only add it once to the document as a global stylesheet.

import buttonLightDomCSS from './button-lightdom.css' assert { type: 'css' };
document.adoptedStyleSheets.push(buttonLightDomCSS);

Currently, I only utilize the custom elements selector. I don't target the slotted elements. As an example:

action-button:focus-visible {
	outline: var(--_outline-width) solid var(--_outline-color);
}

Fingers crossed, nobody is styling outlines with the universal selector like this.

fieldset > *:focus {
	outline: 4px solid Highlight;
}

I couldn't imagine anybody doing this. However, I do need to weigh up the risk of the various contexts where the Web Component is being used. And sometimes I can't style the light DOM because of this.

@MrHBS
Copy link

MrHBS commented Aug 11, 2023

We really should have gotten this over web components.

@dutchcelt
Copy link

We really should have gotten this over web components.

Shadow DOM is very different from Scoping. I do like that you get to keep the cascade with Scope. Especially when not everything is a component.

@dmitriid
Copy link

My 5¢ why scoped css, and not web components.

Apart from the reasons outlined in Web Components Community Group: 2022 Spec/API status [1]

There are too many footguns in Web Components that can be solved or will be solved in some distant future with Javascript only (anything from yet-unsolved cross-root ARIA [2] to ongoing issues with form participation [3]). If we're talking about style encapsulation, it makes sense (especially to library and component authors) to want a CSS-only solution that doesn't require users rely on Javascript, or SSR, or...


[1]

It's worth noting that many of these pain points are directly related to Shadow DOM's encapsulation. While there are many benefits to some types of widely shared components to strong encapsulation, the friction of strong encapsulation has prevented most developers from adopting Shadow DOM, to the point of there being alternate proposals for style scoping that don't use Shadow DOM. We urge browser vendors to recognize these barriers and work to make Shadow DOM more usable by more developers.

[2] From the same 2022 Spec/API status

Shadow boundaries prevent content on either side of the boundary from referencing each other via ID references. ID references being the basis of the majority of the accessibility patters outlines by aria attributes, this causes a major issue in developing accessible content with shadow DOM.

[3] Form-associated custom elements cannot submit forms WICG/webcomponents#814

@keithjgrant
Copy link

keithjgrant commented Aug 25, 2023

@emilio

I mean, shadow dom does provide scoping capabilities, even though perhaps not in the way everybody likes. I agree @scope is much more powerful in principle, but I'm a bit concerned about providing different ways of doing the same thing

I think the key disconnect here, is that it's not a different way to do the same thing. It's a way to do an entirely different thing — though we confusingly tend to use the term "scope" to refer to both things. Shadow DOM doesn't really provide scope; it provides isolation.

When I develop a component for a web application (or for a design system), I don't want to cut a hole out of the page and drop in a totally isolated piece of shadow DOM. I want to layer my component into the cascade that already exists in the light DOM. I want the component have all the styles of the outer page, but to also have priority to selectively override them. And I want it to relinquish that priority to any nested components within it. I need to be able to nest those scopes arbitrarily deep.

Shadow DOM is absolutely the right tool for some jobs. But not the ones I generally work on.

(I blogged a bit more about this at https://keithjgrant.com/posts/2023/08/scope-vs-shadow-dom/)

Again, asking from ignorance: which kind of design-system-component thingie couldn't be made into a web component?

I don't think this is the right question. We have both grid and flexbox. Sure, you can use only grid to lay out everything on the page, but there are plenty of use cases where flexbox is simply the better choice. We need different tools to approach different problems.

What if I don't need a web component? Say my design-system-component is a simple box, with a light gray background and border. But maybe I want links inside that box to have different styling than those outside. This is just a few simple lines of CSS. But as soon as I place another component inside that one, I want the inner component styles to take precedence, while still being influenced by the outer component styles.

The thing with design systems isn't really that you need one specific component to take precedence over others — it's that the entire page is made up of these components. It's about how they need to interact with one another. They are nested dozens of layers deep. I'm not going to make a web component for each and every piece of my web application, especially when many of them are little more than a couple CSS rulesets. But I want the cascade to be usable in such a way that components can influence their child component styles, while still leaving the ability for the child components to override styles from outer components.

I don't want them isolated, I want their scopes to nest and overlap in a controllable way.

@mayank99
Copy link

mayank99 commented Aug 27, 2023

I would like to add what I think makes @scope so special, and why it's not the same as shadow DOM or nesting. In my view, scoping is the single biggest missing feature in CSS. Combined with @layer, it would give developers full control over the cascade in both directions.

These are what I consider the three pillars of @scope:

  • Nearest ancestor proximity. Already discussed above, so I won't elaborate.
    • Example: nested theming (also discussed plenty)
  • Lower boundary aka donut scoping. This is not achievable today without techniques like adding a unique class/attribute to all DOM elements that must be within the donut.
    • Example: A component that needs to style only its own markup and still have room for a slots.
      <div data-component=card>
        <h3>
          <a href='/'>
           <span data-slot> <!-- comes from elsewhere --> </span>
          </a>
        </h3>
        <p>
          <span data-slot> <!-- comes from elsewhere --> </span>
        </p>
      </div>
      @scope ([data-component='card']) to ([data-slot]) {
        /* these selectors will not affect anything inside the slots */
        div { /* ... */ }
        h3 { /* ... */ }
        a { /* ... */ }
        p { /* ... */ }
      }
    • It might appear shadow DOM can do the same thing, but it does not (at least not without some elbow grease). The key here is that styles within @scope will not leak out, but styles from outside can still leak in. This is desirable in the vast majority of cases when writing CSS that utilizes the cascade (e.g. global styles need to still apply to elements within @scope, and sometimes you might also need overrides/exceptions for a particular instance of a component).
  • Implicit upper bound, achieved by omitting the <scope-start> selector.
    • Example: This is incredibly useful for leaf nodes, just because of how simple and intuitive it is.
      <div>
        <style>
          @scope {
            p { /* ... */ }
            input { /* ... */ }
          }
        </style>
        <p>this is scoped</p>
        <input>
      </div>
      
      <p>this is unaffected</p>
      <input>
    • See [css-scoping] Please bring back scoped styles w3c/csswg-drafts#3547 for developer sentiment on this.
    • Shadow DOM can sorta do the same thing, but again you give up the benefits of the cascade while also running into all sorts of issues with shadow DOM (some mentioned above, like form participation and cross-root ARIA).

Hopefully the need for this feature is clearer now. I do think there should be more discussion about the performance concerns. It would be nice to get some insight from the Chrome team on how they were able to tackle it.

@kizu
Copy link

kizu commented Sep 30, 2023

I would like to add another use case for CSS scopes that might be very powerful, but is almost impossible to do correctly without jumping through a lot of hoops today: allowing users to override parts of the UI via custom styles.

This includes emails (which notoriously have issues with custom styles), social media posts (why not allow users to style their posts with CSS, see Cohost for example), custom profile styles (think LiveJournal) and so on.

Modern CSS already provides a few helpful tools that would help with this, like containment, but if users would also get the ability to add any custom styles, scoped to either their profiles and/or their posts, where they could write any selectors, as long as they only apply to a carefully selected scope, — that would unlock so many possibilities!

There might be other ways of handling this (Shadow DOM, notorious escaping and allow-listing of the provided styles), but none of them would provide the same experience and would be as easy to implement.

With containment and scoped styles, it would be much easier for these sites' owners to provide their users tools for customization and creativity, and the smaller & tighter communities could ensure less misuse and abuse of these tools.

The earlier web very often provided a lot of space for creativity and experimentation, with people sharing snippets of CSS and coming up with creative selectors to shape the web, with CSS Zen Garden showing how you could keep HTML intact and still achieve almost anything with just CSS.

The modern web did lose a lot of its charm with the unification of corporate social networks. But I can see how the new wave of projects like federated social networks can really take this idea, as the scope of every instance is much smaller.

@gnat
Copy link

gnat commented Oct 5, 2023

This project is living in the future: https://github.com/gnat/css-scope-inline

Keep the syntax simple and short, friends. I want to see <style scoped> return too, but let's try our best to cut down the excess verbosity.

@collimarco
Copy link

I would love to have:

  • @scope to use inside CSS files (when I use a separate CSS file for a component)
  • <style scoped> to write styles directly in the same file of a component (e.g. when using https://viewcomponent.org)

That would drastically improve the organization of CSS (e.g. in a Rails app). It will be even better than BEM, when combined with the native CSS nesting.

@DarkWiiPlayer
Copy link

This project is living in the future: https://github.com/gnat/css-scope-inline

Keep the syntax simple and short, friends. I want to see <style scoped> return too, but let's try our best to cut down the excess verbosity.

The current iteration of scoped styles is actually relatively close to this; it's a little bit more verbose, sure, but not by much. Taking the example from the linked project:

<div>
    <style>
        @scope {
            :scope { background: red; } /* ✨ :scope */
            button { background: blue; } /* style child elements inline! */
        }
    </style>
    <button>I'm blue</button>
</div>

@bramus
Copy link

bramus commented Oct 6, 2023

Some of our top partners (think some of the really really big sites that you most likely use on a weekly – if not daily – basis) have let us know that they would love to see this feature land across browsers for them to start using.

@CrisFeo
Copy link

CrisFeo commented Oct 6, 2023

This project is living in the future: https://github.com/gnat/css-scope-inline
Keep the syntax simple and short, friends. I want to see <style scoped> return too, but let's try our best to cut down the excess verbosity.

The current iteration of scoped styles is actually relatively close to this; it's a little bit more verbose, sure, but not by much. Taking the example from the linked project:

<div>
    <style>
        @scope {
            :scope { background: red; } /* ✨ :scope */
            button { background: blue; } /* style child elements inline! */
        }
    </style>
    <button>I'm blue</button>
</div>

I'm curious: could we drop the :scope { and just treat prelude-less @scopes that contain styles directly like they're nested? It jives better with existing CSS syntax around nesting in my mind and for the use case of prelude-less @scopes (inline <style>s) its unambiguous what is being targeted while decreasing boilerplate. example:

<div>
    <style>
        @scope {
            background: red;
            button {
                background: blue;
            }
        }
    </style>
    <button>I'm blue</button>
</div>

@zaygraveyard
Copy link

@CrisFeo How would :scope:hover be represented in that case? (Or is it not supported in current version?)

@CrisFeo
Copy link

CrisFeo commented Oct 6, 2023

I would imagine the same way nested styles handle it (and scoped actually supports): using &:hover

@keithjgrant
Copy link

I don't like how that appears like nested CSS, but behaves differently -- notably, the specificity is different. Also, what is the expected specificity of background: red there? Is :scope implied for [0,1,0]? What does that do to the semi-nested button selector? I feel like this opens a big can of worms in a spec that's already been pretty thoroughly hashed through. (this thread should probably move to w3c rather than spamming mozilla, too)

@CrisFeo
Copy link

CrisFeo commented Oct 6, 2023

Totally makes sense, yeah this is bikeshedding on the spec, not mozilla's concern. Damn, that's a good point RE specificity - its definitely a false equivalency.

FWIW, I imagined the button selector would function just like the button selector in the actual proposed version with :scope with the same specificity as the @scope spec implies for it.

@SebastianZ
Copy link
Contributor

Please discuss any specification issues at https://github.com/w3c/csswg-drafts/labels/css-cascade-6! This issue here is to discuss Mozilla's position on this feature.

Sebastian

@nex3
Copy link

nex3 commented Oct 17, 2023

Chiming in from Google's CSS infrastructure team to say that we were very much hoping to see this feature land so we can have a better scoping solution than "forbid all applications from ever using anything but class selectors".

@dshin-moz
Copy link
Contributor

I think - to sum it up, the added utility is there (Altough nesting helps with some cases), the main concern is the performance tradeoff.

All that said, I think we can call it Worth Prototyping, at least.

@keithjgrant
Copy link

Safari has enabled @scope in the latest tech preview: https://developer.apple.com/documentation/safari-technology-preview-release-notes/stp-release-185

@nileshprajapati
Copy link

nileshprajapati commented Jan 27, 2024

Safari will add support @scope in iOS 17.4 https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes

@zcorpan
Copy link
Member

zcorpan commented Jan 29, 2024

@dshin-moz can you create a PR to add this to the dashboard? ("Worth prototyping" is now called "positive".)

@zcorpan zcorpan added ready to add Appears ready to add to the table of positions. position: positive labels Jan 29, 2024
@zcorpan zcorpan closed this as completed in 59e2cca Feb 5, 2024
@zcorpan zcorpan removed the ready to add Appears ready to add to the table of positions. label Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
position: positive venue: W3C Specifications in W3C Working Groups
Projects
None yet
Development

No branches or pull requests