- From: Lea Verou via GitHub <sysbot+gh@w3.org>
- Date: Tue, 24 Sep 2024 00:44:04 +0000
- To: public-css-archive@w3.org
LeaVerou has just created a new issue for https://github.com/w3c/csswg-drafts: == [css-scoping] Allow WCs to expose a subset of their shadow tree, which can then be styled with regular CSS == _(This is a **very ambitious proposal** that I don’t imagine would gain much support from implementors anytime soon — however I think there’s still value in filing these "north star UIs", as we often find a way down the line)_ ## Background The need to expose certain shadow DOM elements to styling from the outside is well-recognized. However, the current mechanism of using `::part()` pseudo-elements suffers from poor ergonomics, still doesn’t fully cover authors' use cases, and constantly brings up new design questions, such as: - https://github.com/w3c/csswg-drafts/issues/10787 - https://github.com/w3c/csswg-drafts/issues/10807 - https://github.com/w3c/csswg-drafts/issues/10794 - https://github.com/w3c/csswg-drafts/issues/10788 - https://github.com/w3c/csswg-drafts/issues/10525 - https://github.com/w3c/csswg-drafts/issues/4412 - https://github.com/w3c/csswg-drafts/issues/9951 Furthermore, I’ve seen many (most?) components adding `part` attributes on _almost_ every element in their shadow DOM. I would go as far as to say that for _most_ WCs I’ve seen using parts, more than half of their shadow DOM elements are exposed via. For example, take a look at this [list of parts from Shoelace’s `<sl-tab-group>`](https://shoelace.style/components/tab-group#parts). This is not only tedious for WC authors, it makes things harder for WC _users_ as they need to learn the various part names, understand what type of element each part corresponds to and where it stands in the hierarchy and there is no way to target relationships between parts, even if every element involved is a `part`. `open-stylable` shadow roots is one solution to this problem, but it’s an all-or-nothing solution that requires giving up encapsulation entirely. ## Proposal I’ve been wondering what could describe author intent more directly here. The intent is to keep certain elements encapsulated (e.g. wrappers) while exposing others that WC users may want to customize, ideally as a tree. What if they could do _just that_? tl;dr: **Authors can expose a _subset_ of their shadow tree that can be styled from the outside with regular CSS.** MVP: 1. An HTML attribute opts an element in to being exposed (name TBB, e.g. `export`). - If used without a value, it only shallowly exports that one element. This is the MVP. 2. We introduce a combinator (could re-introduce `>>>` but with this distinct meaning rather than the previous "anything goes" semantics) that pierces into the shadow DOM, but **only has access to that exposed subtree**. - The subtree *only* consists of exposed elements, but within it everything works as expected, child combinators, sibling combinators, tree pseudos, you name it. - The matching root (i.e. the part that follows after `>>>`) does not need to be at the top-level, it can be anywhere on the tree. This means that `my-component >>> [part=foo]` is essentially `::part(foo)` with better ergonomics. Going further: - We may decide to make this even more granular, with values to not expose element names, classes, ids, certain attributes etc. - A special value (e.g. `export="subtree"`) could export an entire subtree. If the IDL attribute is available on shadow roots, an entire shadow root can be opted in to this with 1 loc. - In that case, there could be a counter attribute to _remove_ subtrees as well (donut scope). Nice synergies: - If we _do_ end up introducing `/slotted/` as a combinator (see #7922), it works with these subtrees out of the box, no need to introduce even more syntax about what you can have after a `::part()` (see #10807 ). - We could introduce syntax that allows ARIA to also hook into this exposed subtree ### Example Suppose we have a `<foo-spinner>` with this structure and these `export` attributes: ```html <foo-spinner> <template shadowrootmode="open"> <div class="wrapper"> <input export> <div class="buttons"> <button class="increment" export>+</button> <button class="decrement" export>-</button> </div> </div> </template> </foo-spinner> ``` This would expose the following subtree that `>>>` would "see": ```html <foo-spinner> < :: exposed subtree > <input> <button class="increment">+</button> <button class="decrement">-</button> < / :: exposed subtree > </foo-spinner> ``` This means that selectors like `foo-spinner >>> .increment:active + .decrement` actually work. Or even `foo-spinner > input:not(:blank) ~ button`, even though that matches on a tree relationship that does not actually exist in the shadow tree. ### Issues 1. Because the parent-child relationships are not necessarily the same as in the shadow DOM, applying CSS properties that depend on parent-child relationships, such as flexbox and grid could have surprising results. However, `::part()` also has this issue and probably any mechanism that allows exposing only a subset of the tree. One solution could be to define that exposition preserves the general shape of the tree, and non-exposed nodes simply cannot be targeted, but this seems both harder to spec, harder to implement _and_ harder to conceptualize for authors. ### Open questions - Do nested shadow trees need another `>>>` or once you have one it has access to the _flattened_ exposed subtree? Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/10939 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Tuesday, 24 September 2024 00:44:05 UTC