Csswg-drafts: [css-scoping] Please bring back scoped styles

Created on 22 Jan 2019  Â·  55Comments  Â·  Source: w3c/csswg-drafts

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

css-cascade-5 css-scoping-1

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.

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.

All 55 comments

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:

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

  2. 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:

  1. Most of the time I want access to global / parent styles for my components.
  2. If I _don't_ want that, I have the all property to unset stuff anyway.
  3. What I _don't_ have is the ability to simply scope, as the spec' once promised, any component specific styles

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

bold only
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

Was this page helpful?
0 / 5 - 0 ratings