-
Notifications
You must be signed in to change notification settings - Fork 73
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
Comments
cc @heycam |
I think my biggest concern here is performance, tbf. Depending on how this interacts with the cascade you need to either:
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 |
@emilio Honest question: if you do the latter, do you expect performance to be any worse than a descendant combinator?
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 |
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...) |
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. |
I think this is a duplicate of #625. |
@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... |
No indication of a conclusion in issue 6790. |
It has not been resolved. |
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.
The similarities have come up in multiple CSSWG issues, and I think we've done a good job exploring how they interact ( |
Issue 6790 has been resolved in favor of weak scoping |
@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 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. |
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 |
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 |
I would love to see this implemented (I would definitely use it in Rails together with ViewComponents) |
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... |
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. (FWIW, Webkit has also indicated support at WebKit/standards-positions#13) |
Curious, can you elaborate? I'm also interested into use cases for leaking some styles but not others.
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 |
Trivial example: Say my page has a series of frequently used utility classes, such as I suppose you could take effort to define "common" styles like this and ensure that stylesheet is 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.
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 |
I was planning on writing an article about the
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 |
I'm the framework lead of Docusaurus (docs React SSG from Meta) and we'd really like to use 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.
More context here: facebook/docusaurus#6032 I don't have the skills to figure out if it's a good decision to add
|
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. |
Another thing I didn't see mentioned explicitly as of yet is that 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:
<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. |
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 (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. |
@dutchcelt Can you provide a more concrete example? I'm confused about:
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
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? |
I was on PTO, so sorry for the lag replying. Some replies / questions in non-specific order (sorry for the mega-reply too):
Sure, that's fair. Though adding another feature to solve the same problem is generally adding complexity, rather than removing it, IMO.
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
I mean, shadow dom does provide scoping capabilities, even though perhaps not in the way everybody likes. I agree
This is not true, see declarative shadow dom (whatwg/dom#831 / #335).
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 Thanks. |
@emilio Yes, I can. My apologies for some assumptions about the design implementation on my part.
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.
The approach I'm currently taking is adding styles from the Web Component itself by adding the light DOM stylesheet as an 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. |
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. |
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]
[2] From the same 2022 Spec/API status
[3] Form-associated custom elements cannot submit forms WICG/webcomponents#814 |
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/)
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. |
I would like to add what I think makes These are what I consider the three pillars of
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. |
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. |
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 |
I would love to have:
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. |
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> |
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. |
I'm curious: could we drop the <div>
<style>
@scope {
background: red;
button {
background: blue;
}
}
</style>
<button>I'm blue</button>
</div> |
@CrisFeo How would |
I would imagine the same way nested styles handle it (and scoped actually supports): using |
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 |
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 |
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 |
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". |
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. |
Safari has enabled |
Safari will add support |
@dshin-moz can you create a PR to add this to the dashboard? ("Worth prototyping" is now called "positive".) |
Request for Mozilla Position on an Emerging Web Specification
Other information
The text was updated successfully, but these errors were encountered: