The scoped style tag HTML attribute works really well with popular frameworks and is far simpler to use than shadow DOM.
e.g. in React:
import React from 'react';
const Profilecard = (props) => {
const styles = `
div {background-color: ${props.bgColor}}
h2 { color: white; }
img {
border-radius: 50%;
height: 80px;
width: 80px;
}`
return (
<div className="card">
<style scoped>{styles}</style>
<h2>{props.name}</h2>
<img src="man3.jpg" alt=""/>
</div>
);
};
export default Profilecard;
Shadow DOM offer more encapsulation than scoped styles, but this level of encapsulation is not often needed, and even sometimes complained about by developers.
e.g. Chris Coyier:
Personally, I wish it was possible to make the shadow DOM one-way permeable: styles can leak in, but styles defined inside can't leak out.
It is important to note that both Vue js and Svelte have replicated this API - it is clearly an API that is both easy to work with and gives the _right level_ of encapsulation (unlike shadow DOM, which is useful for more niche cases).
At the moment people are using many different solutions (CSS Modules, Styled Components and all the other CSS-in-JS options). It would be great if there was a standardized way to solve the problem of scoping rather than the very fragmented options in user-land. I strongly doubt that shadow DOM will prove to be a popular solution capable of attracting people away from these libraries.
A prior discussion about the topic can be found here: https://github.com/w3c/csswg-drafts/issues/137
I would love something like this. I've been experimenting with a custom HTML tag I call <style-container>
that acts as a container for scoping CSS styles, and I'm also exploring the idea of interpreting contained media queries as container queries based off the <style-container>
tag's dimensions, here's how I'm working with it:
<style-container>
<div>Make me 500+ px wide</div>
<style media=none>
div {
border: 1px solid;
padding: 1em;
}
@media (min-width: 500px) {
div {
background: lime;
}
}
</style>
</style-container>
<script type=module>
import styleContainer from 'https://unpkg.com/style-container'
styleContainer()
</script>
From what I can see there are two main benefits:
Having something like this would be good for modularizing code - you could safely include stylesheets in certain HTML elements and be sure they weren't going to apply to other elements.
Another potential benefit — is it possible that interpreting media queries as based on the containing element's dimensions could be a way that existing CSS written with media queries can be re-used for modular responsive styles without having to refactor existing styles or introducing new syntax?
@o-t-w I also, would like to see the re-visiting of scoped styles and would love to see this make a return. Being able to define a style that is scoped to a subset of elements would go a long way in terms of major use cases such as branding, consistency and the application of design systems in general.
@tomhodgins You bring up a good point, what should this spec exactly cover? Simply the scoped keyword? As you mentioned there are lots of ideas that could bleed into this such as media queries, @if
and @else
. Would these other points belong in an elements or container query spec? or would you want to have them all added under the scoped styles spec?
If memory serves me, it was the vendors that pushed back on the spec initially. Maybe we need to revisit their concerns and see what can be done to mitigate them as a start?
I would be in favor of focusing on the scoped html attribute and possibly the reinstatement of the @scoped keyword. Limiting the spec, I think, would help make it a lot more palatable for browser vendors.
I think we need to separate the discussion of scoped styles as a concept from the specifics of the scoped
attribute on a <style>
element. There are other syntaxes that could be used (an @-rule, maybe?) that could serve the same goal with different side effects and fallback behavior.
If memory serves me, it was the vendors that pushed back on the spec initially. Maybe we need to revisit their concerns and see what can be done to mitigate them as a start?
I fully support this strategy.
Can anyone outline specifically why decisions were made to not implement the scoped
attribute? For me, the major issue was progressive enhancement. I couldn't accept the fallback behavior to be the scoped styles applying to the entire document—although this is really only an issue when the scoped styles are in the markup, since you can always test for attribute support in JS.
But maybe there were other reasons from a browser performance perspective?
I'd like to think that by rolling this back to the underlying needs and concerns, we can come up with a different syntax that addresses the needs without the same concerns.
The reason why the scoped
attribute idea was abandoned is that, in contrast to ShadowDOM, it didn't come with any lower boundary. This means that styles affect an entire subtree rather than a single "component". In fact this is not different from prefixing every selector with a unique id eg. .foo { }
becomes #scope-unique-id .foo {}
.
Svelte, Vue, Angular but also styled-jsx (React) instead scope the styles to a single component to simulate ShadowDOM. eg:
<style>
div { color: red }
div h1.foo { color: green }
</style>
<div>
<h1 class="foo">howdy</h1>
</div>
in those frameworks becomes:
<style>
div.scoped-123.scoped-123 { color: red }
div.scoped-123 h1.foo.scoped-123 { color: green }
</style>
<div class="scoped-123">
<h1 class="foo scoped-123">howdy</h1>
</div>
This is done at build time.
The already existing effort to implement it in Blink was actually removed because developers found it to be too complicated to develop scoped
and ShadowDom in parallel. Developers said this would not be a final nail in the coffin of scoped
and that it could be implemented later (e.g. internally based on ShadowDom?). See https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/JB8nFQXhAuQ
Edge didn’t implement it for whatever reason, but this does not seem relevant anymore since Microsoft ditched its effort to develop an own browser engine altogether.
Firefox on the other hand did implement scoped
. They removed it mainly because it was removed from the specs after other browser engine vendors did not implement it. https://groups.google.com/forum/#!topic/mozilla.dev.platform/iBoROFkR9V8
Interestingly, per the current CSS Cascading Level 4 spec,
Normal declarations from style attributes are considered to be scoped to the element with the attribute
Doesn't this imply that, once CSS Nesting makes it into standard, it would be possible to write element-scoped styles as nested selectors inside the style
attribute?
+1 to scoped styling making a return.
Here's how I see it:
all
property to unset stuff anyway.Shadow DOM really does not help, because it creates an _arbitrary_ barrier (except for custom properties, but that's not actually enough; values only, not declarations and no mixin support).
Svelte, Vue, Angular but also styled-jsx (React) instead scope the styles to a single component to simulate ShadowDOM.
This does not simulate Shadow DOM. Shadow DOM prevents inheritance. The Vue behaviour (they actually emulate <style scoped>
exactly) is more desirable than Shadow DOM.
Shadow DOM doesn't prevent inheritance (it used to in v1). eg. color
crosses the SD boundaries https://jsfiddle.net/6ntd0cgw/
To be clear: shadow DOM does _not_ prevent inheritance, but it does prevent global selectors *
, p
, etc from affecting the component. Therefor Svelte, Vue, Angular and styled-jsx are far more comparable to the scoped
attribute than to shadow DOM. As Heydon said, Vue _explicitly_ emulates <style scoped>
it does prevent global selectors *, p, etc from affecting the component
true
Therefor Svelte, Vue, Angular and styled-jsx are far more comparable to the scoped attribute than to shadow DOM. As Heydon said, Vue explicitly emulates
Not 100% sure about Vue (I think I checked once) and Angular but Svelte and styled-jsx act more like Shadow DOM. I am the co-author of styled-jsx and convinced Rich Harris (Svelte's creator) to switch from emulating <style scoped>
to behave like styled-jsx.
update Vue also works like Svelte and styled-jsx https://codesandbox.io/s/ko4kp4nq5r https://vue-loader.vuejs.org/guide/scoped-css.html#mixing-local-and-global-styles
Anyway this thread is not about frameworks I guess :) <style scope>
affects the entire subtree and technically who wants to emulate this behavior could just use a unique id to scope styles to a subtree.
Eventually I don't mind if this makes it to the spec again but I don't think it is a proper solution the the lack of encapsulation in lite DOM.
In fact this is not different from prefixing every selector with a unique id eg.
.foo { }
becomes#scope-unique-id .foo {}
.
I don’t think this is accurate. When scope was in the spec, it was defined such that inner scopes overrode outer scopes, regardless of selector specificity.
For example, given:
/* in one scope */
.foo p {
color: blue;
}
/* in another scope */
.bar p {
color: red;
}
A foo
paragraph inside a bar
would be blue, and a bar
paragraph inside a foo
would be red (assuming each is used in its own scope, nested). Which addresses precisely the problem CSS-in-JS seeks to solve, and would be hugely beneficial. Unfortunately, rules about scoping were removed from the Cascade working draft after the Scope spec got dropped.
I also thought the final version of the spec, which relied on a @scope
at-rule rather than an html attribute was much better. (Here’s my write-up on that)
@keithjgrant thanks for your reply that's useful information! I never looked at the @scope
proposal.
Correct me if I am wrong: when using the inline (html attribute) version would styles cascade? If yes even in the case of #foo p
inside of a #bar b
the former would be blue
and vice versa. Unless you meant that in the "inline" version the inner scope always wins regardless of the specificity (I can imagine that being true for the at-rule version).
Either ways the problem I am talking about is that with this model and no lower boundaries (like in JS components or Shadow DOM) it is impossible to stop styles from leaking into the entire subtree. The perfect example is you are making a website using some templating system. A partial has a p
that you didn't mean to style but @scope .foo { p { margin: 20px } }
will match that and possibly break something (in your example you always reset but that's not always the case). Not sure if it is clear what I am trying to say.
Ah, yes. I see what you’re saying. I think you’re right.
The question of styles “leaking in” is an interesting one. Because I think in most cases, there are some style you would want to leak in, and others you wouldn’t.
Stepping back a bit, I think what I/we really want here is a first-class way to distinguish between base styles and “module” styles. Then you could define a bunch of base styles for the page (font family, color, default margins, etc.); these you would want to “leak in” to everything, as your default baseline. Then, on top of that, a way to define modular styles (module A has a blue heading, and some larger margins, module B has these borders, etc.); these you would want to scope only to their respective modules, and not leak in or out.
Perhaps these modular styles can be done via Shadow DOM, but without a set of base styles that do pierce into all modules, they come up lacking.
CSS Scoped Style spec proposal (@scope) which was removed due to lack of interest: https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#scope
Cascade rules for it: https://www.w3.org/TR/2018/CR-css-cascade-4-20180828/#cascading
To be clear: shadow DOM does not prevent inheritance, but it does prevent global selectors *, p, etc from affecting the component. Therefor Svelte, Vue, Angular and styled-jsx are far more comparable to the scoped attribute than to shadow DOM. As Heydon said, Vue explicitly emulates
<style scoped>
This is correct, I think (largely? Depends on the kind of properties?) But that makes it even worse, because it's so particular about what works and what doesn't. Yes: inheritance; no: global styles? When would you ever want to _just_ inherit, but not have access to the global styles? Who thinks and works like that?
CSS Scoped Style spec proposal (@scope): https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#scope
@fantasai This looks good promising and interesting. To be clear, is this a new proposal you recently wrote?
One concern is the limitation of using selector identifiers. Would this preclude from doing...
<custom-element>
<div></div>
<style scoped>
div {
max-width: 60ch;
}
</style>
</custom-element>
... where max-width: 60ch
refers to just this instance of <custom-element>
?
Sorry if I have understood it poorly.
@Heydon It's a proposal that was put together many years ago (the draft is dated 2014). It was dropped because nobody seemed to be interested in it. Work continued on Web Components, and the scoped style proposals were deleted from the specs.
Wrt your example, doing exactly that would require bringing back HTML scoped styles, which is a different syntax for triggering the same behavior as @scope. In either case, you're binding some styles to a scoping root: the HTML syntax implies that relationship by placing the
This is working well for our purposes (building up huge documents from many smaller ones with `<xi:include>`). We found for completeness we needed another value: `scoped=override`, which is added to a `<style>` to force the declarations to be applied even in an element with `scope="isolate"`
```html
<style scoped="override">
span { color: red }
</style>
<style>
span { font-style: italic }
</style>
<div scoped="isolate">
<style>
span { font-weight: bold }
</style>
<span>bold only, but also red</span>
</div>
The need to impose upper and lower limits on the styles - scoping the scope, if you like - seems to be adding a lot of complexity in many of the comments, particularly when it's attempted from within the stylesheet itself (eg with @scope). It seems to me the decision on whether an element is a scope root belongs with the element itself, not with a stylesheet it includes.
@faceless2
The issue with your approach is the same as with <style scoped>
, in that non‑supporting user agents will ignore the [scoped]
 attribute and apply the <style>
s to all <span>
s, instead of just the scoped ones.
@ExE-Boss Let only those who still write sites with IE6 compatibility worry about that. They can simply not use <style scoped>
if they don't want to.
Yes, but limited support for a new HTML feature is not a new problem. Trying to solve this in the CSS domain purely because HTML lacks an equivalent of @supports
doesn't seem like a better approach to me.
@ExE-Boss Let only those who still write sites with IE6 compatibility worry about that. They can simply not use
<style scoped>
if they don't want to.
Note that a <style scoped>
approach would also affect all _current_ browsers until they implement that feature and those versions get adopted. Without support, the website layout is broken.
have to be repeated in the HTML
It's not something I should bother about when creating DOM dynamically, IMHO.
You definitely should, maybe not for two or three <style scoped>
, but that doesn't scale well. If you have a significant amount of them on a page, the size of the HTML gets bloated and the parsing performance decreases much faster than with centralized solution.
Another idea related to the @scope
rule in combination with HTML is to introduce a scope
attribute naming the scope. The @scope
rule then matches that name.
Example:
<style>
p { color: blue; }
@scope main {
p { color: green; }
}
@scope note {
p { color: gray; }
}
</style>
<p>This text is blue</p>
<section scope="main">
<p>This text is green</p>
<div scope="note">
<p>This text is gray</p>
</div>
</section>
<div scope="note">
<p>This text is gray</p>
</div>
Sebastian
Most helpful comment
I think we need to separate the discussion of scoped styles as a concept from the specifics of the
scoped
attribute on a<style>
element. There are other syntaxes that could be used (an @-rule, maybe?) that could serve the same goal with different side effects and fallback behavior.I fully support this strategy.
Can anyone outline specifically why decisions were made to not implement the
scoped
attribute? For me, the major issue was progressive enhancement. I couldn't accept the fallback behavior to be the scoped styles applying to the entire document—although this is really only an issue when the scoped styles are in the markup, since you can always test for attribute support in JS.But maybe there were other reasons from a browser performance perspective?
I'd like to think that by rolling this back to the underlying needs and concerns, we can come up with a different syntax that addresses the needs without the same concerns.