w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 652 forks source link

[css-scoping] Please bring back scoped styles #3547

Closed o-t-w closed 1 year ago

o-t-w commented 5 years ago

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

tomhodgins commented 5 years ago

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?

ghost commented 5 years ago

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

AmeliaBR commented 5 years ago

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.

giuseppeg commented 5 years ago

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.

prlbr commented 5 years ago

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

SelenIT commented 5 years ago

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?

Heydon commented 5 years ago

+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).

Heydon commented 5 years ago

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.

giuseppeg commented 5 years ago

Shadow DOM doesn't prevent inheritance (it used to in v1). eg. color crosses the SD boundaries https://jsfiddle.net/6ntd0cgw/

o-t-w commented 5 years ago

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>

giuseppeg commented 5 years ago

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