[csswg-drafts] [css-scoping] Light-DOM "slots" (donut holes within donuts) (#6574)

nolanlawson has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-scoping] Light-DOM "slots" (donut holes within donuts) ==
To motivate this discussion, I'd like to quote [the CSS Scoping explainer](https://css.oddbird.net/scope/explainer/):

> Existing tools would still be able to provide syntax sugar for single-file components -- automatically generating the from/to clauses -- but move the primary functionality into CSS.

As a framework author (Lightning Web Components in my case), I totally agree with this goal! We're looking into light-DOM style scoping, and `@scope` as a potential solution. It would be great if frameworks like Vue and Svelte could also migrate their existing solutions to use `@scope` under the hood. So this issue is about a potential gap between [the CSS Scoping proposal](https://drafts.csswg.org/css-scoping-2/) and existing solutions.

In [Vue](https://v3.vuejs.org/guide/component-slots.html) and [Svelte](https://svelte.dev/docs#slot) (as well as other frameworks), it's possible to have light-DOM "slots," which work similar to `<slot>` in shadow DOM. If I understand the proposal, though, it doesn't handle this scenario.

Consider [this example](https://svelte.dev/repl/a86aeb7609fd49f6af927fa7704ab469?version=3.42.4) (using Svelte, although other frameworks would also apply):

```svelte
<!-- Outer.svelte -->
<div>Outer</div>
<Inner>
  <div>Outer (slotted)</div>
</Inner>
<div>Outer</div>
<style>
  div { color: blue; }
</style>
```

```svelte
<!-- Inner.svelte -->
<div>
  <div>Inner</div>
  <slot></slot>
  <div>Inner</div>
</div>
<style>
  div { color: red; }
</style>
```

This would render:

```html
<div>Outer</div>             <!-- blue -->
<div>
  <div>Inner</div>           <!-- red -->
  <div>Outer (slotted)</div> <!-- blue -->
  <div>Inner</div>           <!-- red -->
</div>
<div>Outer</div>             <!-- blue -->
```

In this case, styles defined in `Outer` should apply both to the "outer" elements as well as the element slotted into the `Inner` component. But the following rule would not work:

```css
@scope (.outer) to (.inner) {
  div { color: blue; }
}
```

The rule would not apply to `<div>Outer (slotted)</div>`, as it's under the lower scope boundary (`.inner`).

Note that I'm assuming that the framework is generating the classes `outer` and `inner` for scoping, e.g.:

```html
<div class="outer">Outer</div>
<div class="inner">
  <div>Inner</div>
  <div class="outer">Outer (slotted)</div>
  <div>Inner</div>
</div>
<div class="outer">Outer</div>
```

Since frameworks will be generating the upper and lower scope boundary selectors, a possible workaround is to generate as many scopes as necessary to handle the "donut holes within donuts" (or [Timbits](https://company.timhortons.com/us/en/menu/timbits.php) if you're Canadian) – with potentially infinite recursion, since slotted content can contain other slottable components. However, this would get tedious, as it requires duplicating CSS rules:

```css
@scope (.outer) to (.inner) {
  div { color: blue; }
}
@scope (.outer-slot-1-start) to (.outer-slot-1-end) {
  div { color: blue; }
}
@scope (.outer-slot-2-start) to (.outer-slot-2-end) {
  div { color: blue; }
}
/* etc. */
```

One potential fix could be to allow multiple `@scope`s to close over the same stylesheet:

```css
@scope (.outer) to (.inner), 
@scope (.outer-slot-1-start) to (.outer-slot-1-end), 
@scope (.outer-slot-2-start) to (.outer-slot-2-end),
/* etc. */ {
  div { color: blue; }
}
```

Another potential fix is to have the spec clarify whether scopes are recursive, e.g.:

```html
<style>
  @scope (.outer) to (.inner) {
    h1 { color: blue; }
  }
</style>
<div class="outer">
  <div class="inner">
    <div class="outer">
      <h1>Blue?</h1>
      <div class="inner"></div>
    </div>
  </div>
</div>
```

Recursive scopes get tricky, though, for selectors that cross the lower boundary. Consider [this example](https://svelte.dev/repl/06beafe73fb145a1bd989768dc7477d0?version=3.42.4) (using Svelte again):

```svelte
<!-- App.svelte -->
<div class="red">
  <Slottable>
    <div>Red</div>
  </Slottable>
</div>
<div class="blue">
  <Slottable>
    <div>Blue</div>
  </Slottable>
</div>
<style>
  .red div { color: red; }
  .blue div { color: blue; }
</style>
```

```svelte
<!-- Slottable.svelte -->
<div class="slottable">
  <slot></slot>
</div>
```

(Credit to @pmdartus for the example.)

As far as I can tell, it would not be possible to use `@scope` to reproduce the effect here, because the descendant selectors `.red div` and `.blue div` each cross the lower boundary. ("Red" and "blue" are maybe bad examples – you can imagine dark mode vs light mode, LTR vs RTL, etc.)

I'm not sure how common this pattern is, or if the spec can be changed to accommodate it. But in any case, it is something that frameworks like Vue and Svelte would have to consider when migrating to `@scope`, since it's currently supported in their implementations.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/6574 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 2 September 2021 19:16:19 UTC