Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[open-stylable] Collection of user stories #1052

Open
michaelwarren1106 opened this issue Mar 18, 2024 · 39 comments
Open

[open-stylable] Collection of user stories #1052

michaelwarren1106 opened this issue Mar 18, 2024 · 39 comments

Comments

@michaelwarren1106
Copy link

michaelwarren1106 commented Mar 18, 2024

Let’s let this issue serve as the single place to collect user stories around the “open-stylable shadow roots” proposal.

#909

Submit user stories as comments and I will do my best to collate theme here in this top comment for easy viewing/referral.

User Stories

  • As a component author, I want to build unstyled components that only manage behavior. I want to give my consumers full control and achieve maximum compatibility with their existing styling solutions. I am ok with the tradeoff that DOM changes can break consumer's styles
  • "As a website author, I want to use shadow DOM for my own components (that I author and consume myself). I want to use my existing CSS system, including any global resets, utility classes, and hand-rolled scoped classes. Crucially, I want these styles to be usable in DSD without causing FOUC."
  • As a page author/WC user, I would like to have a way to say, as @chriscoyier put it, "I know what I'm doing" and go wild overwriting anything I want (and not be at the mercy of the WC author).
  • As a performance minded web app developer, I would like a way to benefit from the styles encapsulation of the shadow DOM to avoid style recalculation's performance hit as described by @captainbrosset's at CSS Day 2023 (slides).
  • As an application developer developing apps for company-internal use, I want to re-use web components that represent common logic across various projects regardless of what tech stack they individually use. When frameworks are used that require extra classes, I want to extend the components to use templates and have the framework classes available automatically inside the component.
  • As a web app I’ve already written the styles for various user interface elements (e.g. buttons, form controls). Now I have a larger composition that I need to repeat throughout the app which includes a number of these elements (e.g. a modal dialog that includes a few buttons) and I would like to use the shadow DOM for this. The component doesn’t need the composition slots provide. Parts require me to rewrite my CSS. I would love to write the styles for these elements once, then allow the web components in my app use them, ideally without requiring my entire stylesheet also be applied to the shadow root.
@mayank99
Copy link

Pasting my comment:

  • "As a component author, I want to build unstyled components that only manage behavior. I want to give my consumers full control and achieve maximum compatibility with their existing styling solutions. I am ok with the tradeoff that DOM changes can break consumer's styles."
  • "As a website author, I want to use shadow DOM for my own components (that I author and consume myself). I want to use my existing CSS system, including any global resets, utility classes, and hand-rolled scoped classes. Crucially, I want these styles to be usable in DSD without causing FOUC."

@robglidden
Copy link

From shadow layers proposal (noting they may be a bit over-broad particularly as to coordination scenarios):

Use Cases Addressed

  • Include all document CSS as a low priority layer inside a shadow tree. This is the heart of a solution to the original issue as opened.
  • Support both declarative and imperative use. Declarative shadow trees are fully supported from the onset, as well as imperative use cases.
  • Include selected document CSS as a layer inside a shadow tree. Selected named outer layers can be brought into a shadow tree by either a shadow tree designer or shadow tree user.
  • Enable layer reprioritization. Inner and outer context layers can be intermixed and reprioritized.
  • Enable shadow tree designers to bring in document CSS without intervention by a shadow tree user. If desired, a shadow tree designer can bring in all of or just selected layers of the document outer context CSS, without intervention of the shadow tree user.
  • Enable coordination between shadow tree designers and shadow tree users. Either can bring in and repriortize outer layers.
  • Enable shadow tree user to add and repriortize layers originally provided by shadow tree designer. A user can bring in any chosen outer layer, not just those pre-defined by a designer.
  • Enable a shadow tree designer to "advertise" to shadow tree users the CSS priorities of a shadow tree's inner context. A designer can expose, publish, or document the original layer order of a shadow tree, so that a user can knowingly adjust it or add to it.
  • Polyfillable. As hopefully implied by this proof of concept implementations.

@michaelwarren1106
Copy link
Author

From shadow layers proposal (noting they may be a bit over-broad particularly as to coordination scenarios):

Use Cases Addressed

  • Include all document CSS as a low priority layer inside a shadow tree. This is the heart of a solution to the original issue as opened.
  • Support both declarative and imperative use. Declarative shadow trees are fully supported from the onset, as well as imperative use cases.
  • Include selected document CSS as a layer inside a shadow tree. Selected named outer layers can be brought into a shadow tree by either a shadow tree designer or shadow tree user.
  • Enable layer reprioritization. Inner and outer context layers can be intermixed and reprioritized.
  • Enable shadow tree designers to bring in document CSS without intervention by a shadow tree user. If desired, a shadow tree designer can bring in all of or just selected layers of the document outer context CSS, without intervention of the shadow tree user.
  • Enable coordination between shadow tree designers and shadow tree users. Either can bring in and repriortize outer layers.
  • Enable shadow tree user to add and repriortize layers originally provided by shadow tree designer. A user can bring in any chosen outer layer, not just those pre-defined by a designer.
  • Enable a shadow tree designer to "advertise" to shadow tree users the CSS priorities of a shadow tree's inner context. A designer can expose, publish, or document the original layer order of a shadow tree, so that a user can knowingly adjust it or add to it.
  • Polyfillable. As hopefully implied by this proof of concept implementations.

these are great! would you mind taking a swing at framing these use cases in terms of user stories? i definitely agree that any solution must have some/or all of these baked in, but the current phrasing makes them sound more like features of a solution and less like stories that describe particular, specific user needs/wants.

@robglidden
Copy link

Filtering as a potentially key requirement:

To me, the key open question that needs to be answered is whether selective adoption of document-level stylesheets is necessary or not. That is, should a component or users of the component need to be able to filter (i.e. selectively apply) which stylesheet or style rules apply to its shadow tree.

answering yes, filtering needed, with related essential requirements:

Use cases are intertwined.

Whether simple (I just want my page resets to work in my shadow tree) or most painful (component and context are controlled by different entities) or in between, use cases trace to the same shadow encapsulation, and loosening encapsulation will likely impact many if not all in some way.

Declarative shadow DOM is required.

To me, declarative shadow DOM is required from the onset not just because it is a new thing so logically it must be supported too, but because shadow trees are just HTML and they are what are doing the encapsulating to loosen. And they are a part of the solution.

So Javascript-only solutions are basically out. And polyfillability though not a strict requirement is in this case something of an acid test for completeness.

But also declarative shadow trees already by design address opt-in for imperatively-created web components.

Context-aware opt-in protection is needed.

I don't think an opt-in flag or mode that just brings in all page styles would work as expected in all cases. Some shadow styles could unexpectedly lose. Or get pulled back into a specificity race that could feel like shadow trees just gave up.

Prioritization is required.

Or maybe just unavoidable, since unlayered styles are of necessity assigned a layer priority. And a limited form of priority for shadow trees already exists in the context step, so any brought-in styles would pass through that step too.

@rniwa
Copy link
Collaborator

rniwa commented Mar 18, 2024

Declarative shadow DOM is required.

To me, declarative shadow DOM is required from the onset not just because it is a new thing so logically it must be supported too, but because shadow trees are just HTML and they are what are doing the encapsulating to loosen. And they are a part of the solution.

So Javascript-only solutions are basically out. And polyfillability though not a strict requirement is in this case something of an acid test for completeness.

What are concrete user scenarios that necessitates declarative shadow DOM support? i.e. what exactly is one building to encounter this need?

Context-aware opt-in protection is needed.

I don't think an opt-in flag or mode that just brings in all page styles would work as expected in all cases. Some shadow styles could unexpectedly lose. Or get pulled back into a specificity race that could feel like shadow trees just gave up.

Again, what are concrete user scenarios that necessitates context-aware opt-in?

Prioritization is required.

Or maybe just unavoidable, since unlayered styles are of necessity assigned a layer priority. And a limited form of priority for shadow trees already exists in the context step, so any brought-in styles would pass through that step too.

Again, what are concrete user scenarios that necessitates prioritization of CSS rules?

@michaelwarren1106
Copy link
Author

michaelwarren1106 commented Mar 19, 2024

Filtering as a potentially key requirement:

To me, the key open question that needs to be answered is whether selective adoption of document-level stylesheets is necessary or not. That is, should a component or users of the component need to be able to filter (i.e. selectively apply) which stylesheet or style rules apply to its shadow tree.

answering yes, filtering needed, with related essential requirements:

Use cases are intertwined.

Whether simple (I just want my page resets to work in my shadow tree) or most painful (component and context are controlled by different entities) or in between, use cases trace to the same shadow encapsulation, and loosening encapsulation will likely impact many if not all in some way.

Declarative shadow DOM is required.

To me, declarative shadow DOM is required from the onset not just because it is a new thing so logically it must be supported too, but because shadow trees are just HTML and they are what are doing the encapsulating to loosen. And they are a part of the solution.

So Javascript-only solutions are basically out. And polyfillability though not a strict requirement is in this case something of an acid test for completeness.

But also declarative shadow trees already by design address opt-in for imperatively-created web components.

Context-aware opt-in protection is needed.

I don't think an opt-in flag or mode that just brings in all page styles would work as expected in all cases. Some shadow styles could unexpectedly lose. Or get pulled back into a specificity race that could feel like shadow trees just gave up.

Prioritization is required.

Or maybe just unavoidable, since unlayered styles are of necessity assigned a layer priority. And a limited form of priority for shadow trees already exists in the context step, so any brought-in styles would pass through that step too.

i understand all of these as possible solution requirements. in order to frame them, i would still love to see each of these connected to a direct user need in terms of the typical user story framing.

how do these items get put into a sentence like:

“As a {person} i would like {feature/requirement} so that I can {do a specific thing}.

i think this exercise and framing will help tie stuff like “prioritization is required” to a direct reason WHY prioritization is required in terms of a person and their ability/non-ability to build things.

@zaygraveyard
Copy link

zaygraveyard commented Mar 19, 2024

Two non-mutually exclusive use cases concerning styling shadow DOM elements from outside the component that I wish can be supported (but not sure if they can be part of this initiative/proposal):

  • As a page author/WC user, I would like to have a way to say, as @chriscoyier put it, "I know what I'm doing" and go wild overwriting anything I want (and not be at the mercy of the WC author).
  • As a performance minded web app developer, I would like a way to benefit from the styles encapsulation of the shadow DOM to avoid style recalculation's performance hit as described by @captainbrosset's at CSS Day 2023 (slides).

EDIT: On second thought, they might be mutually exclusive, not sure 🤔

@DarkWiiPlayer
Copy link

DarkWiiPlayer commented Mar 19, 2024

Here's an attempt at counting how many of these user stories imply a need for:

  • A pull-based system whereby CEs autonomously adopt styles from the outside
  • A push-based system whereby websites can force style rules into shadow trees
  • Granularity in what is adopted that can be satisfied via layers (but doesn't have to be layers; see this comment)
  • The ability for combinators in css selectors to reach across shadow-boundaries
User pull push layers combinators
@mayank99 2 0 1 0?
@zaygraveyard 0 1 0 0
@DarkWiiPlayer 2 0 2 1
@knowler 1 0 1 0
@robglidden 1 2 1 0
Total 6 3 5 1

I will attempt to update this list as more user-stories come in (as time allows). Feel free to suggest additional considerations to track.

@DarkWiiPlayer
Copy link

When developing web applications for company-internal use, I want to re-use web components that represent common logic across various projects regardless of what tech stack they individually use. When frameworks are used that require extra classes, I want to extend the components to use templates and have the framework classes available automatically inside the component.

@robglidden
Copy link

@michaelwarren1106, @rniwa:

It seems to be getting missed that modern browsers support "Declarative Shadow DOM".

The "user" in the above use cases and requirements is just the user of a shadow tree, particularly a declarative shadow tree.

To elaborate:

  • Shadow trees are "just HTML" (a document fragment attached to a particular element, for which certain css rules apply).
  • Shadow trees are not "Custom Elements" i.e elements upgraded by customElements.define() to have a Javascript class instance associated with them. This kind of "Custom Element" is often informally called a "web component" or a "Web Component".
  • "Custom Elements" don't encapsulate anything. Shadow trees do the encapsulating.

Declarative shadow DOM already gives web component users a way to present a shadow tree (with user-written CSS styles in it) to the web component. And a way for the web component to accept, reject, or use those styles as it sees fit.

So user stories that boil down to something like "as a web component user I want the web component to consider using some styles I present to it in a shadow tree" is not a missing platform feature, it is a choice for web component authors to make.

What is missing is a way to bring page styles into shadow trees.

Without this common context, there seems to be a conflation loop.

Classifying as "push" vs "pull" vs "other" seem to suffer similarly.

@michaelwarren1106
Copy link
Author

michaelwarren1106 commented Mar 19, 2024

user stories that boil down to “as a user i need X so that Y” is precisely for the purpose of making sure we don’t conflate things and are crystal clear about what to ask implementers for and why.

if the user stories you are thinking of are more specific than “as a web component user” then that’s great! let’s capture those under a different persona. like “as a developer using shadow trees with declarative shadow dom, i need X so that Y”.

what i think implementers are asking for, and the purpose of my creating this issue is about finding all the Xs and the Ys.

we all have different opinions on the “what do people want” it’s the “so that Y”s that really matter. specifying what devs are unable to do today, or what things are difficult/cumbersome today is the entire reason to change things. calling some feature being “a missing platform feature” isn’t as powerful as describing specifically what that new feature will accomplish that matters.

in the other thread, and now starting in this one, there’s been a lot of “i think it should be this way” and not enough “if it is {this way} then {persona} can do {thing} way easier/simpler/faster/better etc etc“.

we need to get at the specifics of what folks are trying to do. enumerate those first. THEN work on solutions that solve those cases. as of yet we’ve been working backwards from opinions about things we think are needed and not working forward from actual outlined spelled out problems.

@DarkWiiPlayer
Copy link

@robglidden

It seems to be getting missed that modern browsers support "Declarative Shadow DOM".

Declarative Shadow DOM is an optimisation to avoid FOUC by pre-rendering the contents of a shadow DOM on the server or during deployment. It's not an entirely new way of doing shadow DOM, just an additional API to attach and initialise it. The problems with styling remain the exact same ones: Outside style-sheets don't affect the insides of the shadow DOM.

  • Shadow trees are not "Custom Elements"

I think you're missing the forest for the trees here, no pun intended. Shadow DOM is primarily useful in combination with custom elements and <template>s to build web components. They can be attached to other elements, yes, but that is a very rare way of using them, so it makes sense for most of these user stories to focus on this main use-case.

If you have any user stories that showcase the use of shadow-dom with normal HTML elements, I'd be curious to see those to get another perspective on the spec, but if you don't actually post them here, then we can't read your mind.

So user stories that boil down to something like "as a web component user I want the web component to consider using some styles I present to it in a shadow tree" is not a missing platform feature, it is a choice for web component authors to make.

It is not per se a missing feature, as it can be done imperatively using Javascript, but it is a common enough case for a specific platform feature to be added to make it more ergonomic and resilient. Synchronising stylesheets between light- and shadow-dom is far from trivial with the APIs we currently have.

Classifying as "push" vs "pull" vs "other" seem to suffer similarly

I disagree. Classifying as "push" vs "pull" is very important because it is the distinction between whether the component author or the component user is the one who initiates the style adoption. The open-stylable proposal specifically addresses the former case, where a component author wants to opt in to some or all styles defined by the component user. It preserves encapsulation except where the author specifically chooses to expose APIs to let the user influence the process.

Separating user stories into which of the two they need is important to know whether a push-based mechanism is worth exploring further, either as a separate feature, or as a part of open-stylable, or a purely pull-based mechanism can cover most of the users' needs.

Again, if you think there's another, more important axis to consider, it'd be much easier if you could explain it with some actual user stories or at least specific use cases. I think this issue was created precisely because just listing requirements seems a bit arbitrary to us strangers on the internet, and user stories are a convenient way of bundling a requirement with the context to show why that requirement is useful.

@robglidden
Copy link

@michaelwarren1106:

No objection from me, user stories are valuable.

I am saying something different.

The primary use case #909 was opened for in 2020 is component library maintainers who want to move to web components while supporting stylesheets that they don't control. And there are important related use cases.

But now, years later, declarative shadow DOM directly addresses many of these related use cases.

In particular, declarative shadow DOM already provides a way for web component users to present user written styles to web components. In a way that answers the often-expressed need on behalf of web component authors to have opt-in say before accepting those styles into web components.

So I am saying it doesn't make sense to collect user stories for something that was already considered and implemented in web browsers. To me, the problem to solve is bringing page styles into shadow trees, not some other already solved problem.

So no, I am not saying or implying at all that I am thinking of (or being cagey about) user stories that are more specific than "as a web component user". That's backwards.

@robglidden
Copy link

@DarkWiiPlayer:

Declarative Shadow DOM is most powerful when used with "Custom Elements".

Declarative shadow trees were designed exactly to support the case of web component users presenting shadow trees to web component authors to consider using in a more declarative, ergonomic, resilient, and component-author-acceptable way than imperative Javascript.

To me that's the other, more important axis to consider.

@rniwa
Copy link
Collaborator

rniwa commented Mar 19, 2024

That's not really use cases / user stories. Use cases are specific scenarios in which specific piece of software may be used in a specific way, not some general statement about what kind of problem ought to be solved.

@robglidden
Copy link

"as a web component user I want the web component to consider using some styles I present to it in a shadow tree"

That is the user story that is already asked and answered by declarative shadow DOM.

@justinfagnani
Copy link
Contributor

justinfagnani commented Mar 20, 2024

I don't understand the relationship of declarative shadow DOM to the discussion here.

As an implementation detail, not a user story, any new mode or option to attachShadow() would be available as an attribute on the DSD template. Other than that, shadow roots created imperatively and declaratively are basically the same. Open-stylable would obviously be available on both.

Users of a component being able to provide their own shadow root to a custom element is, honestly, a pretty niche use of shadow DOM. I know of no components written this way and I wouldn't want to have to resort to this - which would interfere with the component having it's own shadow root - as a way of theming components. We should be able to do better.

But also... theming is different from open-stylable. Open stylable is a proposal to solve a problem affecting components migrating from light DOM to shadow DOM. There are still lots of other problems around theming that are better solved, IMO, with specific theming APIs that don't require completely opening up a shadow root to styles, and work with ::part().

edit:

Declarative shadow trees were designed exactly to support the case of web component users presenting shadow trees to web component authors to consider using in a more declarative, ergonomic, resilient, and component-author-acceptable way than imperative Javascript.

This is historically incorrect. Declarative shadow DOM was designed to support server-side rendering of shadow roots. Their main use case has been as a serialization of shadow roots that would have been created with attachShadow() - ie, as an internal component feature. In fact, some of the discussion was around how to mitigate the hazard of users placing shadow roots on components they don't own and therefore breaking the component. They were not designed for ergonomics or to be in any way more "component-author-acceptable" than attachShadow().

@michaelwarren1106
Copy link
Author

i feel like i should just close this issue and try to compile user stories elsewhere. this issue is rapidly turning into another debate about solutions and proposals instead of merely listing problems needing solving.

we don’t need another thread for debating proposals, terms and concepts. that will just derail any progress

@robglidden
Copy link

@justinfagnani:

Declarative Shadow DOM can be used on its own as a way to encapsulate styles or customize child placement, but it's most powerful when used with Custom Elements.

is a direct quote from a Google web site.

Please, I would not want to be misinterpreted if I said "Google, meet Google". So I won't say it.

@rniwa
Copy link
Collaborator

rniwa commented Mar 20, 2024

Please don't use this issue to discuss things. That's the point of #909. This issue should focus on collecting specific use cases.

@knowler
Copy link

knowler commented Mar 20, 2024

User story: I’m building a web app. I’ve already written the styles for various user interface elements (e.g. buttons, form controls). Now I have a larger composition that I need to repeat throughout the app which includes a number of these elements (e.g. a modal dialog that includes a few buttons) and I would like to use the shadow DOM for this. Slots are the wrong tool for the job since the component doesn’t need the composition they provide. Parts are equally awkward since they require me to rewrite my CSS. I would love to write the styles for these elements once, then allow the web components in my app use them (ideally without requiring my entire stylesheet also be applied to the shadow root).

Why slots are awkward for this use case?

DSD used purely for example and some details might be left out:

<delete-dialog>
  <template shadowrootmode=open>
    <dialog>
      <form method=post>
        <slot name=confirm-button></slot>
        <slot name=cancel-button></slot>
      </form>
    </dialog>
  </template>
  <button class="button button--danger" slot=confirm-button>Yes, delete it.</button>
  <button class="button button--secondary" slot=cancel-button formmethod=dialog>Cancel</button>
</delete-dialog>

Problem: I’m not interested in making this abstraction super flexible. Needing to use slots here is creating extra work when I want to use the component: I want the component itself to know which buttons to use, what their text content should be, and any sort of other attributes they need. Further, requiring the buttons to be provided as slotted content makes this more prone to failure.

Why parts are awkward for this use case?

DSD used purely for example and some details might be left out:

<delete-dialog>
  <template shadowrootmode=open>
    <dialog>
      <form method=post>
        <button part=confirm-button>Yes, delete it.</button>
        <button part=cancel-button formmethod=dialog>Cancel</button>
      </form>
    </dialog>
  </template>
</delete-dialog>

To style these I need to use ::part(confirm-button) and ::part(cancel-button).

.button,
::part(confirm-button),
::part(cancel-button) { /* button styles */ }

.button--danger,
::part(confirm-button) { /* button danger styles */ }

.button--secondary,
::part(cancel-button) { /* button secondary styles */ }

You might have noticed that these part names aren’t great for mapping to my button classes. They are what I might expect from a custom element though since they are more semantic. It could be better if I wrote my custom element but instead use more presentational part names:

<delete-dialog>
  <template shadowrootmode=open>
    <dialog>
      <form method=post>
        <button part="confirm-button button button--danger">Yes, delete it.</button>
        <button part="cancel-button button button--secondary" formmethod=dialog>Cancel</button>
      </form>
    </dialog>
  </template>
</delete-dialog>

Now when I write my CSS it’s a bit better:

.button,
::part(button) { /* button styles */ }

.button--danger,
::part(button--danger) { /* button danger styles */ }

.button--secondary,
::part(button--secondary) { /* button secondary styles */ }

In this situation the problem might well be solved for this use case, however, it requires a strict one-to-one in regard to part name and class name. If I use a third party component, then I might not get the presentational information that I need from the part names and end up needing to do something like the first parts example. Further, now since my button selector is not just .button in my system, any element that contains buttons needs to select both .button and ::part(button).

Why not wrap your styled elements in custom elements so they can be used in shadow roots?

  • Form associated elements won’t associate with any forms they are a part of—there would need to be extra logic written to make that work (i.e. making the wrapper element a form associated element).
  • Third party custom elements don’t know about my styled custom element, so this requires me to have the styles available to apply to parts that they might expose.
  • I don’t want to modularize my entire CSS component library just to use the Shadow DOM.

Why not all the styles (the entire stylesheet or all the stylesheets)?

Any styles that are allowed into a shadow root will potentially match something in the shadow root, so I personally would like to restrict that to a minimum, since otherwise the benefits of the isolation aspect of style encapsulation completely go away. For example, if I let all the styles in, then I both need to be more careful with how I write my document-level styles and more guarded with how I write my shadow root’s styles. By only allowing in what the component needs (it doesn’t have to necessarily use all of the allowed styles), then I am still able to enjoy all the benefits of style encapsulation and have some external styles to use for my component abstraction.

Note that I am still very much interested in using the existing shadow DOM styling features (i.e. parts, slots, etc.) for styling concrete uses of my components. If any of the concrete uses of my components need more access to its shadow root for styling (e.g. more than the parts API can provide), I would prefer to keep that as an exception and apply those styles specifically to the shadow root that they apply to. I find that this becomes a lot more maintainable since generally the concrete uses of the component are styled with a more robust and declarative styling API (e.g. delete-dialog::part(confirm-button)), while any exceptions are very clearly highlighted as being so.

@rniwa
Copy link
Collaborator

rniwa commented Mar 20, 2024

@knowler : That's some good concrete use case, thank you! Could you elaborate why applying the entire stylesheet is problematic in your use case?

@bkardell
Copy link

bkardell commented Mar 20, 2024

@rniwa just for clarification when you say "the entire stylesheet" do you mean "all of the page level stylesheets"? (or is it tree scoped, or something else?) It's the word "the" I'm asking about I guess. It seems to indicate that we've somehow identified a specific stylesheet, and I'm not sure if you meant that or not?

@robglidden
Copy link

Here are user stories and corresponding Web Platform Tests for the collection of user stories #1052 for "open-stylable" Shadow Roots #909.

You can find the actual web platform tests in the shadow layers proposal repository.

01 Page resets

As a web page author I just want my page resets to work in my shadow tree so that buttons in the shadow tree match buttons outside the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
02 Nested shadow trees

As a web page author or shadow tree designer, I want to use declarative shadow DOM to design a <header-container> for my page so that buttons in nav-bar get their style from <nav-bar>, and the Home button gets its style from the page styles. No Javascript, no Flash of Unstyled Content, no attachShadow() or CustomElements.define().

<body>
  <style>
    @layer buttons {
      button {
        border: thick solid blue;
      }
    }
  </style>
  <header-container>
    <template shadowrootmode="open">
      <style>
        @layer inherit.buttons;
      </style>
      <button>Home</button>
      <nav-bar>
        <template shadowrootmode="open">
          <style>
            @layer buttons {
              button {
                border: thick dashed red;
              }
            }
          </style>
          <button>About</button>
        </template>
      </nav-bar>
    </template>
  </header-container>
</body>
03 Web component from another entity

As a web page author I want to pass some page styles into a web component from another entity whose internal styles I do not control, so that the web component will use those styles.

<body>
  <script type="module">
    import { WebComponentFromAnotherEntity } from "./components/WebComponentFromAnotherEntity.js";
    customElements.define(
      "web-component-from-another-entity",
      WebComponentFromAnotherEntity
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <web-component-from-another-entity>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
    </template>
  </web-component-from-another-entity>
</body>
04 Web component rejects user styles

As a web component author I want to reject any page styles that the web component user offers me to use, so that the styles in the web component are never affected by outer styles.

<body>
  <script type="module">
    import { WebComponentRejectsUserStyles } from "./components/WebComponentRejectsUserStyles.js";
    customElements.define(
      "web-component-rejects-user-styles",
      WebComponentRejectsUserStyles
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <web-component-rejects-user-styles>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
    </template>
  </web-component-rejects-user-styles>
</body>
05 Page styles at low priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in their page styles at a low priority, so that default styles in the shadow tree will win over brought-in page styles.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets buttons;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
06 Page styles at high priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in their page styles at a high priority, so that brought-in page styles will win over default styles in the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer buttons, inherit.resets;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
07 All page styles at low priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in all their page styles at a low priority, so that default styles in the shadow tree will win over brought-in page styles.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit, buttons;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
08 All page styles at high priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in all their page styles at a high priority, so that brought-in page styles will win over default styles in the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer buttons, inherit;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
09 Web component all page styles low priority

As a web component author I want to bring in all page styles at a low priority without the web component user doing anything, so that default web component styles will win over brought-in page styles.

<body>
  <script type="module">
    import { WebComponentAllPageStylesLowPriority } from "./components/WebComponentAllPageStylesLowPriority.js";
    customElements.define(
      "web-component-all-page-styles-low-priority",
      WebComponentAllPageStylesLowPriority
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <web-component-all-page-styles-low-priority>
  </web-component-all-page-styles-low-priority>
</body>
10 Web component all page styles high priority

As a web component author I want to bring in all page styles at a high priority without the web component user doing anything, so that brought-in page styles will win over default web component styles.

<body>
  <script type="module">
    import { WebComponentAllPageStylesHighPriority } from "./components/WebComponentAllPageStylesHighPriority.js";
    customElements.define(
      "web-component-all-page-styles-high-priority",
      WebComponentAllPageStylesHighPriority
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <web-component-all-page-styles-high-priority>
  </web-component-all-page-styles-high-priority>
</body>

@rniwa
Copy link
Collaborator

rniwa commented Mar 21, 2024

@rniwa just for clarification when you say "the entire stylesheet" do you mean "all of the page level stylesheets"? (or is it tree scoped, or something else?) It's the word "the" I'm asking about I guess. It seems to indicate that we've somehow identified a specific stylesheet, and I'm not sure if you meant that or not?

I'm talking about the stylesheet referred to in "ideally without requiring my entire stylesheet also be applied to the shadow root". Not sure if knowler meant all of page's stylesheets, or a specific stylesheet imported by link / @import. I'd like to understand what use cases lead to the need to filter, or selectively apply, style rules because that seems like a key question here as I mentioned in #909 as well.

@knowler
Copy link

knowler commented Mar 21, 2024

Could you elaborate why applying the entire stylesheet is problematic in your use case?

@rniwa I updated my comment above (under “Why not all the styles (the entire stylesheet or all the stylesheets)?”) to answer your question.

Not sure if knowler meant all of page's stylesheets, or a specific stylesheet imported by link / @import. I'd like to understand what use cases lead to the need to filter, or selectively apply, style rules because that seems like a key question here as I mentioned in #909 as well.

For the use case above, I meant that I don’t want all of the document’s stylesheets. That use case is more flexible in regards to how I structure/split up my stylesheets, so if I needed to split out the styles I want to be included in the shadow roots into a separate stylesheet, then I wouldn’t have any issue doing that. I guess one potential complication is that I am likely using layers in the document (especially to facilitate any code splitting), but in the shadow root I might have a different layering structure or no layering structure, so that would need to be considered if a singular mechanism for applying to styles to both the document and shadow root is used.

@DarkWiiPlayer
Copy link

DarkWiiPlayer commented Mar 21, 2024

Inspired by the recent comment in the feature issue:

As a website author, I want to structure my CSS however works best for my project and make as little changes as possible to achieve compatibility with the web components I want to or may in the future want to use. When changes have to be made, they should be generic and semantic and not specific to the components.

As an added example: If I want a component <contact-form> to inherit resets.css and framework.css from the light-dom, I should not be expected to somehow mark those two files as used by contact form, but at best instruct the <contact-form> to use two files, and at worst mark those two files as "resets" and "basic styles / framework" in a way that works or can work for any other similar component from different authors.

In other words, every mechanism for selecting to use styles from resets.css but not from footer.css should work regardless of whether those two files are linked using a <link> tag, an @import tag or are inlined in a <style> tag.

@DarkWiiPlayer
Copy link

As a component author, I want to build components that default to inheriting all the outside styles, but allow users to narrow down what styles get inherited. I want to define my own API for this to have proper control over how much flexibility I want to give to my users.

@DarkWiiPlayer
Copy link

As a component author, I want to inherit the outside styles not into the entire shadow-dom, but into a scope to make sure outside styles cannot mess with certain parts of my component like a top level grid wrapper. For example when populating the shadow-dom with my own HTML, then inserting generated HTML into that structure.

@DarkWiiPlayer
Copy link

As a website author / component user, I want components to follow the surrounding theme and not end up with, e.g., a dark-mode contact form in a light-mode website or vice versa.

I bring this one up because it might depend on selectors like .dark button { color: white } detecting a button inside a shadow DOM attached to a descendant from a class="dark" element. I suspect getting combinators to work like this might cause some massive headache to the browser people, so I want to make extra sure these problems get discussed as early as possible.

@knowler
Copy link

knowler commented Mar 21, 2024

As a website author / component user, I want components to follow the surrounding theme and not end up with, e.g., a dark-mode contact form in a light-mode website or vice versa.

I bring this one up because it might depend on selectors like .dark button { color: white } detecting a button inside a shadow DOM attached to a descendant from a class="dark" element. I suspect getting combinators to work like this might cause some massive headache to the browser people, so I want to make extra sure these problems get discussed as early as possible.

@DarkWiiPlayer Some of those cases are potentially solvable with container style queries using custom properties (e.g. @container style(--theme: dark)). Not quite the same as using a class name, since they can be “intercepted” as they inherit down a tree (but in many ways that would be better for theming).

In any case, this is good to highlight, so thanks for bringing this up.

@robglidden
Copy link

Here are updated user stories and corresponding Web Platform Tests for the collection of user stories #1052 for "open-stylable" Shadow Roots #909.

01 Page resets

As a web page author I just want my page resets to work in my shadow tree so that buttons in the shadow tree match buttons outside the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
02 Nested shadow trees

As a web page author or shadow tree designer, I want to use declarative shadow DOM to design a <header-container> for my page so that buttons in nav-bar get their style from <nav-bar>, and the Home button gets its style from the page styles. No Javascript, no Flash of Unstyled Content, no attachShadow() or CustomElements.define().

<body>
  <style>
    @layer buttons {
      button {
        border: thick solid blue;
      }
    }
  </style>
  <header-container>
    <template shadowrootmode="open">
      <style>
        @layer inherit.buttons;
      </style>
      <button>Home</button>
      <nav-bar>
        <template shadowrootmode="open">
          <style>
            @layer buttons {
              button {
                border: thick dashed red;
              }
            }
          </style>
          <button>About</button>
        </template>
      </nav-bar>
    </template>
  </header-container>
</body>
03 Web component from another entity

As a web page author I want to pass some page styles into a web component from another entity whose internal styles I do not control, so that the web component will use those styles.

<body>
  <script type="module">
    import { WebComponentFromAnotherEntity } from "./components/WebComponentFromAnotherEntity.js";
    customElements.define(
      "web-component-from-another-entity",
      WebComponentFromAnotherEntity
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <web-component-from-another-entity>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
    </template>
  </web-component-from-another-entity>
</body>
04 Web component rejects user styles

As a web component author I want to reject any page styles that the web component user offers me to use, so that the styles in the web component are never affected by outer styles.

<body>
  <script type="module">
    import { WebComponentRejectsUserStyles } from "./components/WebComponentRejectsUserStyles.js";
    customElements.define(
      "web-component-rejects-user-styles",
      WebComponentRejectsUserStyles
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <web-component-rejects-user-styles>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets;
      </style>
    </template>
  </web-component-rejects-user-styles>
</body>
05 Page styles at low priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in their page styles at a low priority, so that default styles in the shadow tree will win over brought-in page styles.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets buttons;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
06 Page styles at high priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in their page styles at a high priority, so that brought-in page styles will win over default styles in the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer buttons, inherit.resets;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
07 All page styles at low priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in all their page styles at a low priority, so that default styles in the shadow tree will win over brought-in page styles.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit, buttons;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
08 All page styles at high priority

As a shadow tree designer I want the user of the shadow tree to be able to bring in all their page styles at a high priority, so that brought-in page styles will win over default styles in the shadow tree.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer buttons, inherit;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
09 Web component all page styles low priority

As a web component author I want to bring in all page styles at a low priority without the web component user doing anything, so that default web component styles will win over brought-in page styles.

<body>
  <script type="module">
    import { WebComponentAllPageStylesLowPriority } from "./components/WebComponentAllPageStylesLowPriority.js";
    customElements.define(
      "web-component-all-page-styles-low-priority",
      WebComponentAllPageStylesLowPriority
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <web-component-all-page-styles-low-priority>
  </web-component-all-page-styles-low-priority>
</body>
10 Web component all page styles high priority

As a web component author I want to bring in all page styles at a high priority without the web component user doing anything, so that brought-in page styles will win over default web component styles.

<body>
  <script type="module">
    import { WebComponentAllPageStylesHighPriority } from "./components/WebComponentAllPageStylesHighPriority.js";
    customElements.define(
      "web-component-all-page-styles-high-priority",
      WebComponentAllPageStylesHighPriority
    );
  </script>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <web-component-all-page-styles-high-priority>
  </web-component-all-page-styles-high-priority>
</body>
11 Designer or author provided template

As a shadow tree designer or web component author, I want to provide users a default template of CSS and/or HTML so that users can knowledgeably bring in page styles.

<body>
  <style>
    @layer shadowbuttons {
      :host(button-group) button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer buttons, inherit.shadowbuttons;

        @layer buttons {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
12 Revert to parent shadow tree

As a shadow tree user, I want to bring in styles from a parent shadow tree so that shadow trees that are children of shadow trees will have styles consistent with the parent shadow trees.

(currently unimplemented in shadow layers proposal)

CSS:

@layer buttons, revert.parentshadowbuttons;

Example:

<body>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
        <style>
            @layer parentshadowbuttons {
                :host(button-group) button {
                    border: thick dashed red;
                }
            }
        </style>
        <button-item>
            <template shadowrootmode="open">
                <style>
                    @layer buttons, revert.parentshadowbuttons;
                    @layer buttons {
                        button {
                            border: thick solid black; }
                    }
                </style>
                <button>button-item inside button-group<button-item>
            </template>
        </button-item>
    </template>
  </button-group>
</body>
13 Polyfillable

As a web author, shadow tree designer or user, or web component author or user I want any solution for bringing in page styles to shadow trees to be polyfillable, so that I can evaluate, test, adopt and deploy it in a timely matter. This seems particularly important for an HTML-parser level feature.

See the user-story-tests folder in the shadow layers proposal repository.

14 Inherit resets layer as higher priority renamed layer

As a web page author I want to bring in my resets layer as a renamed layer so that it can have higher priority than the shadow tree's own reset layer.

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer resets, inherit.resets.as.shadowresets, shadowresets;

        @layer resets {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
15 Inherit resets layer as lower priority renamed layer

As a web page author I want to bring in my resets layer as a renamed layer so that it can have lower priority than the shadow tree's own reset layer.

CSS:

@layer inherit.resets.as.shadowresets, shadowresets, resets;

Example:

<body>
  <style>
    @layer resets {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets.as.shadowresets, shadowresets, resets;

        @layer resets {
          button {
            border: thick solid black;
          }
        }
      </style>
      <button>Button inside a shadow tree</button>
    </template>
  </button-group>
</body>
16 Interweave priorities of outer and inner context layers

As a web page author I want to interweave priorities of outer and inner context layers so some have lower priority of a shadow tree layer and some have higher priority of a shadow tree layer.

CSS:

@layer inherit.A.as.outerA, inherit.B.as.outerB, outerA, A, B, outerB;

Example:

<body>
  <style>
    @layer A {
      button.a {
        border: thick dashed red;
      }
    }
    @layer B {
      button.b {
        border: thick solid blue;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.A.as.outerA, inherit.B.as.outerB, outerA, A, B, outerB;

        @layer A {
          button.a {
            border: thin dashed red;
          }
        }
        @layer B {
          button.b {
            border: thin solid blue;
          }
        }
      </style>
      <button class="a">Button a (inner has priority, thin dashed red)</button>
      <button class="b">Button b (outer has priority, thick solid blue)</button>
    </template>
  </button-group>
</body>
17 Inherit @scope page style

As a declarative shadow tree or web component user, I want to bring into a shadow tree a page style that includes an @scope rule in a layer, so that I can give the outer context @scope rule priority over an inner content @scope rule.

<body>
  <style>
    @layer card-container {
      @scope (section) to (article) {
        header {
          border: thick dashed red;
        }
      }
    }
  </style>
  <card-container>
    <template shadowrootmode="open">
      <style>
        @layer inherit.card-container.as.outer-card-container, card-container, outer-card-container;

        @layer card-container {
          @scope (section) to (article) {
            header {
              border: thick solid black;
            }
          }
        }
      </style>
      <section>
        <header>
          Card Header (thick red dashed) (from outer-card-container)
        </header>
        <article class="content">
          <header>Content Header</header>
          <div>Content</div>
        </article>
      </section>
    </template>
  </card-container>
</body>
18 Inherit named @sheet as layer

As a declarative shadow tree or web component user, I want to bring into a shadow tree a page style that includes an @sheet in a layer, so that I can give the outer context @sheet priority over an inner content styles.

At-rule support detection in @supports is not available, so @sheet would not be polyfillable Multiple stylesheets per file #5629, so the POC does not implement @sheet. However, @layer is widely deployed so polyfillability is not needed for it, and @layer also provides the essential priority mechanism.

Nonetheless, an @sheet supporting syntax would be possible:

//Inherit named sheet as layer
@layer inherit.sheet.mysheet.as.mysheet, mysheet;
19 Inherit unlayered page styles as lower priority layer

As a user or author of a declarative shadow tree or web component I want to bring in unlayered page styles into a shadow tree so that the outer context unlayered page styles will have lower priority than the shadow tree styles.

In CSS:

@layer inherit.unlayered.as.unlayered, unlayered, shadowstyles;

Example:

<body>
  <style>
    button {
      border: thick solid black;
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.unlayered.as.unlayered, unlayered, shadowstyles;

        @layer shadowstyles {
          button {
            border: thick dashed red;
          }
        }
      </style>
      <button>
        Button inside a shadow tree (thick dashed red) (styled from
        shadowstyles)
      </button>
    </template>
  </button-group>
</body>
20 Inherit unlayered page styles as higher priority layer

As a user or author of a declarative shadow tree or web component I want to bring in unlayered page styles into a shadow tree so that the outer context unlayered page styles will have higher priority than the shadow tree styles.

In CSS:

@layer inherit.unlayered.as.unlayered, unlayered, shadowstyles;

Example:

<body>
  <style>
    button {
      border: thick solid black;
    }

    @layer buttons {
      button {
        border: thick dashed yellow;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.unlayered.as.unlayered, shadowstyles, unlayered;

        @layer shadowstyles {
          button {
            border: thick dashed red;
          }
        }
      </style>
      <button>
        Button inside a shadow tree (thick solid black) (styled from unlayered
        page styles)
      </button>
    </template>
  </button-group>
</body>
21 Interweave priorities of outer layered and unlayered styles

As a user or author of a declarative shadow tree or web component I want to bring in both layered unlayered page styles into a shadow tree, so that the outer context layered and unlayered page styles can interweave with the shadow tree styles.

In CSS:

@layer inherit.layered.as.layered, inherit.unlayered.as.unlayered, layered, shadowstyles, unlayered;

Example:

<body>
  <style>
    button {
      border: thick solid black;
    }

    @layer buttons {
      button {
        border: thick dashed yellow;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.layered.as.layered, inherit.unlayered.as.unlayered, layered, shadowstyles, unlayered;
        @layer shadowstyles {
          button {
            border: thick dashed red;
          }
        }
      </style>
      <button>
        Button inside a shadow tree (thick solid black) (styled from unlayered
        page styles)
      </button>
    </template>
  </button-group>
</body>
22 Inherit all outer page styles

As a user or author of a declarative shadow tree or web component I want to bring in both layered unlayered page styles into a shadow tree, so that the outer context unlayered styles have priority over outer context layered styles.

In CSS:

@layer inherit.unlayered.as.unlayered, inherit.unlayered.as.unlayered, layered, unlayered;

Example:

<body>
  <style>
    button {
      border: thick solid black;
    }

    @layer buttons {
      button {
        border: thick dashed yellow;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.layered.as.layered, inherit.unlayered.as.unlayered, layered, unlayered;
      </style>
      <button>
        Button inside a shadow tree (thick solid black) (styled from unlayered
        page styles)
      </button>
    </template>
  </button-group>
</body>
23 Pass through from design system

As a user of a design system and a web component or declarative shadow tree, neither of which I control the styles, I want to pass CSS framework styles into the web component or declarative shadow tree, so that the web component or declarative shadow tree can be styled consistent with the CSS framework.

Example:

<body>
  <style>
    button {
      border: thick solid black;
    }

    @layer design-system {
      button {
        border: thick dashed red;
      }
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.design-system.as.design-system, design-system;
      </style>
      <button>
        Button inside a shadow tree (thick dashed red) (styled from design
        system layer)
      </button>
    </template>
  </button-group>
</body>
24 Pass through from layered CSS framework

As a user of a layer-aware low-priority CSS framework and a web component or declarative shadow tree, neither of which I control the styles, I want to pass CSS framework styles into the web component or declarative shadow tree, so that the web component or declarative shadow tree can be styled consistent with the CSS framework.

Example:

<body>
  <style>
    @layer css-framework {
      button {
        border: thick dashed red;
      }
    }
    button {
      border: thick solid black;
    }
  </style>
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.css-framework.as.css-framework, css-framework;
      </style>
      <button>
        Button inside a shadow tree (thick dashed red) (styled from
        css-framework)
      </button>
    </template>
  </button-group>
</body>

@robglidden
Copy link

Additional user stories and corresponding web platform tests for the collection of user stories #1052 for "open-stylable" Shadow Roots #909.

25 Inherit @imported CSS file

As a shadow tree or web component user, I want to inherit into a shadow tree a CSS file that I have already @imported into the page outside the shadow tree, so that I don't have to @import the same CSS file twice (once into the page, and again into the shadow tree).

In other words, I don't want to have to do this:

resets.css file:

button {
  border: thick dashed red;
}

Double @import I don't want to do:

<body>
  @import assets/resets.css;
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      @import assets/resets.css;
      <button>
        Button inside a shadow tree (thick dashed red) (double imported
        resets.css)
      </button>
    </template>
  </button-group>
</body>

See also Allow authors to apply new css features (like cascade layers) while linking stylesheets #7540 and Provide an attribute for assigning a <link> to a cascade layer #5853.

Example:

<body>
  @import assets/resets.css layer(resets);
  <button>Button outside a shadow tree</button>
  <button-group>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets.as.outerresets, outerresets;
      </style>
      <button>
        Button inside a shadow tree (thick dashed red) (from inherited
        resets.css file)
      </button>
    </template>
  </button-group>
</body>
26 Page styles only affecting shadow tree

As a shadow tree or web component user, I want to provide some page styles outside a shadow tree that won't affect the page at all but can be inherited into the shadow tree, so that I can write page styles that only affect shadow trees.

  • Note that when evaluated in the context of a shadow tree, :host( <compound-selector> ) matches the shadow tree’s shadow host if the shadow host, in its normal context, matches the selector argument. In any other context, it matches nothing.
  • :host behaves similarly in any other context.
<style>
  @layer button-group-styles {
    :host(button-group) button {
      border: thick dashed red;
    }
  }
</style>
<button>Button outside a shadow tree (not thick dashed red)</button>
<button-group>
  <template shadowrootmode="open">
    <style>
      @layer inherit.button-group-styles.as.page-defined-styles, page-defined-styles;
    </style>
    <button>
      Button inside a shadow tree (thick dashed red) (from page layer
      'button-group-styles')
    </button>
  </template>
</button-group>

@knowler
Copy link

knowler commented Mar 28, 2024

Hi all, let’s make this a GitHub Discussion: w3c/webcomponents-cg#92

I think that format will help us keep individual user stories surfaced and will allow us to as further questions to clarify each use case.

Feel free to move your use cases over there. I’ll circle around in the next week to collect any that have been missed.

@robglidden
Copy link

Here are additional user stories and corresponding web platform tests for the collection of user stories #1052 for "open-stylable" Shadow Roots #909.

27 Markdown component with automatic page styles

As a web component author, I want to write a web component that transforms markdown into HTML and brings in page styles, so that transformed markdown is styled from page styles without the web component user having to do anything.

A prototypical <md-block> web component with page styles.

<body>
  <style>
    h2 {
      border: thick dashed red;
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block>
    <script type="text/markdown">
      ## H2 from markdown (thick dashed red) (from page styles)
    </script>
  </md-block>
</body>
28 Markdown component with author-defined page layer

As a web component author, I want to write a web component that transforms markdown into HTML and brings in a page layer with a name that I designate, so that the transformed markdown is styled from the named page layer I designate.

<body>
  <style>
    @layer md-block-styles {
      h2 {
        border: thick dashed red;
      }
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block-with-author-defined-page-layer>
    <script type="text/markdown">
      ## H2 from markdown (thick dashed red) (from page styles)
    </script>
  </md-block-with-author-defined-page-layer>
</body>
29 Markdown component with author-defined lower priority shadow layer

As a web component author, I want to write a web component that transforms markdown into HTML and brings in page styles, so that the transformed markdown is styled from page styles if they exist and if not the transformed markdown is styled by default web component provided styles, without the web component user having to do anything.

<body>
  <style>
    h2 {
      border: thick dashed red;
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block-with-author-defined-lower-priority-shadow-layer>
    <script type="text/markdown">
      ## H2 from markdown (thick dashed red) (from page styles)

      ### H3 from markdown (thick solid black) (from web component default styles)
    </script>
  </md-block-with-author-defined-lower-priority-shadow-layer>
</body>
30 Markdown component with author-defined higher priority shadow layer

As a web component author, I want to write a web component that transforms markdown into HTML and brings in page styles, so that the transformed markdown is styled from page styles, but if web component default styles exist they have higher priority, without the web component user having to do anything.

<body>
  <style>
    h2,
    h3 {
      border: thick dashed red;
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block-with-author-defined-higher-priority-shadow-layer>
    <script type="text/markdown">
      ## H2 from markdown (thick dashed red) (from page styles)

      ### H3 from markdown (thick solid black) (from web component default styles)
    </script>
  </md-block-with-author-defined-higher-priority-shadow-layer>
</body>
31 Markdown component with user-selectable page styles

As a web component author, I want to write a web component that transforms markdown into HTML, and has default markdown styles and user-selectable page styles, so that the transformed markdown is styled by either the defaults or by the styled user-selectable page styles, as the user sees fit.

<body>
  <style>
    @layer resets {
      h2 {
        border: thick dashed red;
      }
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block-with-user-selectable-page-styles>
    <script type="text/markdown">
      ## H2 from markdown (thick dashed red) (from page styles)

      ### H3 from markdown (thick solid black) (from component defaults)
    </script>
    <template shadowrootmode="open">
      <style>
        @layer inherit.resets.as.outerresets, mk-block-defaults, outerresets;
      </style>
    </template>
  </md-block-with-user-selectable-page-styles>
</body>
32 Markdown component with page @scope

As a web component author, I want to write a web component that transforms markdown into HTML, and has default markdown styles and user-selectable page styles, so that the transformed markdown can be styled by a user-provided @scope page style.

<body>
  <style>
    @layer markdown-scope-styles {
      @scope (article) to (li) {
        ul {
          border: thick dashed red;
        }
      }
    }
  </style>
  <h2>H2 outside shadow tree</h2>
  <md-block-with-user-selectable-page-styles>
    <script type="text/markdown">
      ## H2 from markdown

      - list item from markdown (thick dashed red) (from page @scope)

      ### H3 from markdown (thick solid black) (from component defaults)
    </script>
    <template shadowrootmode="open">
      <style>
        @layer inherit.markdown-scope-styles.as.markdown-scope-styles, md-block-default-styles, markdown-scope-styles;
      </style>
    </template>
  </md-block-with-user-selectable-page-styles>
</body>

@robglidden
Copy link

Here are additional user stories and corresponding web platform tests for the collection of user stories #1052 for "open-stylable" Shadow Roots #909.

33 Component library with CSS file and page styles

As a shadow-tree-based component library author or maintainer I want to provide a single, once-imported CSS file for all the components in the library and also bring all page styles into some of the components, so that library and page styles only apply to some or all components as appropriate.

Exemplar imperative components:

  • <md-block>: An imperative (Javascript) component that transforms and displays markdown contained in a <script type="text/markdown"> element. (a prototypical imperative shadow tree component with page styles added, see 27-32 Markdown Component user stories)
  • <counter-button>: An imperative (Javascript) shadow tree component with a clickable counter button. (like a prototypical plain DOM Javascript component)

See user story "25 Inherit imported css file".

<body>
    <style>
        @import url(library/library.css) layer(library);

        h2 {
            border: thick dashed red;
        }

        button {
            border: thick ridge yellow;
        }

        section {
            border: thick solid red;
        }
    </style>
    <section>
        Section outside shadow tree
        <h2>H2 outside shadow tree</h2>
        <button>button outside shadow tree</h2>
    </section>
    <md-block>
        <script type="text/markdown">
## H2 from markdown (thick dashed red) (from page styles)
    </script>
    </md-block>
    <counter-button></counter-button>
</body>
34 Component library with declarative and imperative components and page styles

As a shadow-tree-based component library author or maintainer starting from the component library in "33 Component library with CSS file and page styles", I want to add declarative-only components (components built with declarative shadow DOM and no Javascript), so that the library provides both declarative and imperative components.

Exemplar declarative component:

  • <section-container>: A declarative component (no Javascript declarative shadow tree) that styles semantic HTML elements section, header, and footer. (A platform-encapusulated version of a very commonly implemented web design pattern).
<body>
    <style>
        @import url(library/library.css) layer(library);

        h2 {
            border: thick dashed red;
        }

        button {
            border: thick ridge yellow;
        }

        section {
            border: thick solid black;
        }
    </style>
    <section>
        Section outside shadow tree
        <h2>H2 outside shadow tree</h2>
        <button>button outside shadow tree</h2>
    </section>
    <section-container>
        <template shadowrootmode="open">
            <style>
                @layer inherit, inherit.library.as.library, library;
            </style>
            <section>
                <slot></slot>
            </section>
        </template>
        <div>section-container</div>
        <md-block>
            <script type="text/markdown">
## H2 from markdown (thick dashed red) (from page styles)
      </script>
        </md-block>
        <counter-button></counter-button>
    </section-container>
</body>
35 Component library with multi-component user-settable lower and higher priority page styles

As a shadow-tree-based component library author or maintainer starting from the component library in "34 Component library with declarative and imperative components and page styles", I want to add multi-component user-settable lower and higher priority page styles, so that users can provide page styles that have either lower or higher priority than component default styles.

<body>
    <style>
        @import url(library/library.css) layer(library);

        h2 {
            border: thick dashed red;
        }

        button {
            border: thick ridge yellow;
        }

        section {
            border: thick solid black;
        }

        @layer library-user {
            h4 {
                border: thick ridge red;
            }
        }

        @layer library-user-priority {
            h5 {
                border: thick ridge black;
            }
        }
    </style>
    <section>
        Section outside shadow tree
        <h2>H2 outside shadow tree</h2>
        <button>button outside shadow tree</h2>
    </section>
    <section-container>
        <template shadowrootmode="open">
            <style>
                @layer inherit, inherit.library.as.library, library;
            </style>
            <section>
                <slot></slot>
            </section>
        </template>
        <md-block>
            <script type="text/markdown">
## H2 from markdown (thick dashed red) (from page styles)

### H3 from markdown

#### H4 from markdown (thick ridge red) (from library-user)

##### H5 from markdown (thick ridge black) (from library-user-priority)
      </script>
        </md-block>
        <counter-button></counter-button>
    </section-container>
</body>
36 Component library with individual component user-settable lower and higher priority page styles

As a shadow-tree-based component library author or maintainer starting from the component library in "35 Component library with multi-component user-settable lower and higher priority page styles", I want to add to individual components user-settable lower and higher priority page styles, so that users can bring in and priortize any page style layers they want.

<body>
  <style>
        @import url(library/library.css) layer(library);

        @layer page-layer {
            h2 {
                border: thick dashed red;
            }

            button {
                border: thick ridge yellow;
            }

            section {
                border: thick solid black;
            }
        }
    </style>
    <section>
        Section outside shadow tree
        <h2>H2 outside shadow tree</h2>
        <button>button outside shadow tree</h2>
    </section>
    <section-container>
        <template shadowrootmode="open">
            <style>
              @layer inherit.page-layer.as.page-layer, inherit.library.as.library, library;
            </style>
            <section>
                <slot></slot>
            </section>
        </template>
        <md-block>
            <script type="text/markdown">
## H2 from markdown (thick dashed red) (from page layer)

###### H6 from markdown (thick solid black) (from component defaults)
      </script>
        </md-block>
        <counter-button></counter-button>
    </section-container>

</body>

@michaelwarren1106
Copy link
Author

michaelwarren1106 commented May 1, 2024

All,

I've spent some time going through the use cases and trying to organize and categorize them in a way that might be useful in discussions with browser implementors. I tried to analyze and combine similar use cases into representative use cases and categories. I've also disregarded any use cases that I thought were pointing too directly at a solution like "I want to use css layers to do ...." or "I want to use @sheet to push styles into a component". Personally, I don't think use cases should pre-dispose solutions, only describe problems that are currently difficult to implement in today's world. The solutions discussions come after the use cases, imo.

Here's what I have come up with.

Global CSS Approach/Framework / Reset

  • As a web page author I want my page reset stylesheets to work in my shadow tree so that buttons in the shadow tree match buttons outside the shadow tree.
  • As a website author, I want to structure my CSS however works best for my project and make as little changes as possible to achieve compatibility with the web components I want to or may in the future want to use. When changes have to be made, they should be generic and semantic and not specific to the components.
  • As a component library author, I want to distribute a single shared .css file that styles all my components. I want my consumers to be able to include this stylesheet once on the page and have it be available across all shadow-roots (and nested shadow-roots) in all components from my library without needing JS or adoptedStylesheets
  • As a "UserCSS" user I want to be able to affect all elements in displayed pages including shadow DOM with my recipes.

Building apps completely from web components

  • "As a website author, I want to use shadow DOM for my own components (that I author and consume myself). I want to use my existing CSS system, including any global resets, utility classes, and hand-rolled scoped classes. Crucially, I want these styles to be usable in DSD without causing FOUC."
  • “I’m building a web app. I’ve already written the styles for various user interface elements (e.g. buttons, form controls). Now I have a larger composition that I need to repeat throughout the app which includes a number of these elements (e.g. a modal dialog that includes a few buttons) and I would like to use the shadow DOM for this. Slots are the wrong tool for the job since the component doesn’t need the composition they provide. Parts are equally awkward since they require me to rewrite my CSS. I would love to write the styles for these elements once, then allow the web components in my app use them (ideally without requiring my entire stylesheet also be applied to the shadow root).”
  • As a website author / component user, I want components to follow the surrounding theme and not end up with, e.g., a dark-mode contact form in a light-mode website or vice versa.
  • As a component (or library) author, I have some nested components. I would like to share some common resets and styles at the native element level (buttons, inputs, etc) across all of my sub-components (some of which perhaps are not mine, but are also 'open' to styling, while more generally working with abstractions - shadows/parts.

Web components in “hostile” environments

  • As a part of our WordPress plugin, we have a public facing widget that can be embedded into any page or post. We’re using the shadow DOM since to be frank WordPress is quite hostile: it’s impossible to predict the concoction of different themes and plugins or even custom CSS from the site owner. Our widget is fairly complex — it’s almost an entire app. WordPress being Wordpress, we have users who want the level of customization that they’re used to. Shadow parts are our only option, however, it’s a lot of work to not only add generic parts (e.g. button, etc.) to everything, but also to add context to those parts (e.g. confirm-button; primary-nav-current-nav-link?). It would be great to allow the site owner allow some of their styles in, perhaps their theme’s stylesheet, perhaps maybe just some specific styles for the widget. In any case, allowing all of the document styles in likely won’t serve the user and defeats our purpose in using the shadow DOM.

Inherit styles from a parent root instead of the document

  • As a dev building a chat widget from WCs design to be an “embedded mini-app”, I need the parent WC to be encapsulated from the document and child components to be able to receive styles from the parents root so that the entire widget stays encapsulated but common styles for sub parts can be externalized to the parent and shared from there
  • As a dev building micro-frontend remotes as “mini apps” from WCs I need to be able to put common styles for my app in a global place for my app, but not in the document, and be able to have those styles affect sub components so that i can minimize repeated styles across common sub components

Headless Components

  • "As a component author, I want to build unstyled components that only manage behavior. I want to give my consumers full control and achieve maximum compatibility with their existing styling solutions. I am ok with the tradeoff that DOM changes can break consumer's styles."
  • As a component author, I want to build components that default to inheriting all the outside styles, but allow users to narrow down what styles get inherited. I want to define my own API for this to have proper control over how much flexibility I want to give to my users.
  • As a web component author, I want to write a web component that transforms markdown into HTML and brings in page styles, so that transformed markdown is styled from page styles without the web component user having to do anything.

DSD - Out of order streaming

  • As an application author, I want to use DSD to achieve out of order streaming without the need for JS to replace content when it arrives without also eliminating the use of global styling since DSD out of order streaming requires a shadowRoot on the <body> element.

DSD - reduce repetitive styles

  • As a dev using DSD for my application (either a page author writing DSD, or a component author providing/compiling to DSD), I want to reduce the repetition of styles for each instance of a DSD component on a page by using a single global stylesheet that can be imported/declared once and apply to all DSD instances of components without FOUC.

Open Questions / Use Cases / Features of a solution?

  • Priority? If external styles are applied to shadow roots, is there just a single cascade pass for all styles on a page, in which shadow roots are included? If there are multiple cascade passes, what is the order of cascading? Is there a mechanism for ordering the cascade passes so that either the component’s internal shadow root styles “win” or the external styles “win”?
  • Filtering parts of the shadow DOM that external styles apply to?
    • As a component author, I want to inherit the outside styles not into the entire shadow-dom, but into a scope to make sure outside styles cannot mess with certain parts of my component like a top level grid wrapper. For example when populating the shadow-dom with my own HTML, then inserting generated HTML into that structure.
  • Polyfillable
    • There has been a request that any potential solutions for open-stylable components be polysyllable for testing and gradual adoption
  • External page styles that ONLY affect shadow trees and DO NOT affect the Light DOM?
    • As a shadow tree or web component user, I want to provide some page styles outside a shadow tree that won't affect the page at all but can be inherited into the shadow tree, so that I can write page styles that only affect shadow trees.
  • Component Crowbar - my own personal terminology :)
    • As a page author/WC user, I would like to have a way to say, as Chris Coyier put it, "I know what I'm doing" and go wild overwriting anything I want (and not be at the mercy of the WC author).
    • As a web page author I want to pass some page styles into a web component from another entity whose internal styles I do not control, so that the web component will use those styles.

If I've missed (or disregarded) your use case, and you feel it important to include and appreciably different from the ones captured above, I'm happy to amend the list. If you can phrase your use case in the "As a __, I want ___ so that ___ " format without pre-supposing a solution/approach, I'll add.

@thepassle
Copy link

Thanks for summarizing @michaelwarren1106 🙂 Slight nit on the DSD out of order streaming summary:

- requires a shadowRoot on the element.
+ requires a shadowRoot on the body element.

@michaelwarren1106
Copy link
Author

Thanks for summarizing @michaelwarren1106 🙂 Slight nit on the DSD out of order streaming summary:

- requires a shadowRoot on the element.
+ requires a shadowRoot on the body element.

haha i had it as html and github ignored it hehe. put backticks around it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants