Skip to content

Commit aebf0c3

Browse files
Initial commit
1 parent a060ce8 commit aebf0c3

1 file changed

Lines changed: 55 additions & 40 deletions

File tree

ShadowDOM/explainer.md

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ content location of future work and discussions.
3535
- [Goals](#goals)
3636
- [Non-goals](#non-goals)
3737
- [Use case](#use-case)
38-
- [Media site control widgets](#media-site-control-widgets)
3938
- [Anywhere web components are used](#anywhere-web-components-are-used)
4039
- [Streaming SSR](#streaming-ssr)
4140
- [Alternatives to using style in DSD](#alternatives-to-using-style-in-dsd)
@@ -69,10 +68,10 @@ content location of future work and discussions.
6968
## Background
7069
With the use of web components in web development, web authors often encounter challenges in managing styles, such as distributing global styles into shadow roots and sharing styles across different shadow roots. Markup-based shadow DOM, or [Declarative shadow DOM (DSD)](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom), is a new concept that makes it easier and more efficient to create a shadow DOM definition directly in HTML, without needing JavaScript for setup. [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) provides isolation for CSS, JavaScript, and HTML. Each shadow root has its own separate scope, which means styles defined inside one shadow root do not affect another or the main document.
7170

72-
We're currently investigating this and [@sheet](/AtSheet/explainer.md) in parallel, and anticipate that we'll be prioritizing only one of these two in the immediate future.
71+
[Declarative shadow DOM (DSD)](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom) is a markup-based (declarative) alternative to script-based (imperative) [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM). Imperative Shadow DOM currently supports the [adoptedStyleSheets](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets) property, which allows for sharing stylesheets between shadow roots, but Declarative Shadow DOM does not have a declarative solution for sharing inline styles. This proposal aims to address this gap with the introduction of `<style type="module">`, which defines inline style modules to share, and the `shadowrootadoptedstylesheets` attribute on the `<template>` tag as an analog to Imperative Shadow DOM's `adoptedStyleSheets` property.
7372

7473
## Problem
75-
Sites that make use of Declarative Shadow DOM (DSD) have reported that the lack of a way to reference repeated stylesheets creates large payloads that add large amounts of latency. Authors have repeatedly asked for a way to reference stylesheets from other DSD instances in the same way that frameworks leverage internal data structures to share constructable style sheets via `adoptedStyleSheets`. This Explainer explores several potential solutions.
74+
Sites that make use of [Declarative shadow DOM (DSD)](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom) have reported that the lack of a way to reference repeated stylesheets creates large payloads that add large amounts of latency. Authors have repeatedly asked for a way to reference stylesheets from other DSD instances in the same way that frameworks leverage internal data structures to share constructable style sheets via `adoptedStyleSheets`. This Explainer explores several potential solutions.
7675

7776
Relying on JavaScript for styling is not ideal for DSD for several reasons:
7877
* One of the main goals of DSD is to not rely on JavaScript [for performance and accessibility purposes](https://web.dev/articles/declarative-shadow-dom).
@@ -111,42 +110,9 @@ This document explores several proposals that would allow developers to apply st
111110
Some developers have expressed interest in CSS selectors crossing through the Shadow DOM, as discussed in [issue 909](https://github.com/WICG/webcomponents/issues/909#issuecomment-1977487651). While this scenario is related to sharing styles with Shadow DOM elements, it is solving a different problem and should be addressed separately.
112111

113112
## Use case
114-
### Media site control widgets
115-
Consider a media site that uses control widgets such as play/pause buttons, volume sliders, and progress bars that are implemented as web components with shadow roots. The site might want to share styles between the top-level document and the shadow roots to provide a cohesive look and feel throughout all the site's controls.
116-
117-
```html
118-
<head>
119-
<style>
120-
/* Global styles for the parent document */
121-
...
122-
</style>
123-
</head>
124-
```
125-
Meanwhile, the styles defined within the Shadow DOM are specific to the media control widget. These styles ensure that the widget looks consistent and isn't affected by other styles on the page.
126-
```js
127-
const sheet = new CSSStyleSheet();
128-
sheet.replaceSync(`
129-
// Shared stylesheet for all <media-control> elements.
130-
...
131-
`);
132-
133-
class MediaControl extends HTMLElement {
134-
constructor() {
135-
super();
136-
137-
const shadow = this.attachShadow({ mode: 'open' });
138-
shadow.adoptedStyleSheets.push(sheet);
139-
140-
// Initialize content from template here.
141-
}
142-
}
143-
customElements.define("media-control", MediaControl);
144-
document.body.appendChild(document.createElement("media-control"));
145-
```
146-
Both the controls in the parent document and the controls inside the media control widget are able to share the same base styles through `adoptedStyleSheets`.
147113

148114
### Anywhere web components are used
149-
When asked about pain points in [Web Components](https://2023.stateofhtml.com/en-US/features/web_components/), the number one issue, with 13% of the vote, is styling and customization. Many respondents specifically mentioned the difficulty of style sharing issues within the shadow DOM:
115+
When asked about pain points in [Web Components](https://2023.stateofhtml.com/en-US/features/web_components/), the number one issue, with 13% of the vote, is styling and customization. Many respondents specifically mentioned the difficulty of style sharing issues within a shadow DOM:
150116
* "I want to use shadow DOM to keep the light DOM tidy and use slots, but I don't always want style isolation"
151117
* "Inheriting/passing CSS styles from the main DOM to a shadow DOM"
152118
* "Shadow dom is a nightmare due to inability to style with global styles"
@@ -201,13 +167,13 @@ Step 3: Attach the Constructable Stylesheet to the shadow root:
201167
```js
202168
shadow.adoptedStyleSheets = [constructableStylesheet];
203169
```
204-
The downside of this approach is a potential FOUC, where the element is initially painted without styles, and then repainted with the Constructable Stylesheet.
170+
The downside of this approach is a potential FOUC, where the element is initially painted without styles, and then repainted with the Constructable Stylesheet. Another downside to this approach is that it requires script. Requiring script to apply styles somewhat defeats the purpose of [Declarative shadow DOM (DSD)](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom).
205171
206172
### Using `rel="stylesheet"` attribute
207-
Using `<link rel="stylesheet">` to share styles across Shadow DOM boundaries helps maintain consistent design, reducing style duplication and potentially shrinking component sizes for faster load times. However, it can cause redundant network requests since each component that uses `<link rel="stylesheet">` within its Shadow DOM may trigger an expensive operation such as a network request or a disk access.
173+
Using `<link rel="stylesheet">` to share styles across Shadow DOM boundaries helps maintain consistent design, reducing style duplication and potentially shrinking component sizes for faster load times. However, it can cause redundant network requests since each component that uses `<link rel="stylesheet">` within its Shadow DOM may trigger an expensive operation such as a network request or a disk access. Also note that `<link rel="stylesheet">` is not render blocking when it's in the `<body>` (as Declarative Shadow DOM nodes typically are), which can cause an FOUC.
208174
209175
### CSS `@import` rules
210-
Global styles can be included in a single stylesheet, which is then importable into each shadow root to avoid redundancy. Inline `<style>` blocks do not support `@import` rules, so this approach must be combined with either of the aforementioned Constructable Stylesheets or `<link rel>` approaches. If the stylesheet is not already loaded, this could lead to an FOUC.
176+
Global styles can be included in a single stylesheet, which is then importable into each shadow root to avoid redundancy. The downsides are the exact same as in [Using `rel="stylesheet"` attribute](#using-relstylesheet-attribute), with an additional disadvantage that multiple `@import` statements are loaded sequentially (while `<link>` tags will load them in parallel).
211177
212178
## Proposal: Inline, declarative CSS module scripts
213179
This proposal builds on [CSS module scripts](https://web.dev/articles/css-module-scripts), enabling authors to declare a CSS module inline in an HTML file and link it to a DSD using its [module specifier](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#:~:text=The-,module%20specifier,-provides%20a%20string). A `type=”module”` attribute on the `<style>` element would define it as a CSS module script and the specifier attribute would add it to the module cache as if it had been imported. This allows the page to render with the necessary CSS modules attached to the correct scopes without needing to load them multiple times. Note that [module maps](https://html.spec.whatwg.org/multipage/webappapis.html#module-map) are global, meaning that modules defined in a Shadow DOM will be accessible throughout the document context.
@@ -443,6 +409,53 @@ In this example, the `<template>` element is parsed first. When the `<template>`
443409

444410
The contents of the Declarative CSS Module with `specifier="foo"` (with `color: red`) are then parsed and an [import map](https://html.spec.whatwg.org/multipage/webappapis.html#import-maps) is created as specified above. Since the `<template>` element failed to import a module, the `color: red` styles will not be applied, although subsequent `<template>` elements could adopt a stylesheet with `specifier="foo"` now that it has been defined.
445411

412+
### Use with External CSS Files and `<link rel="modulepreload">`
413+
414+
The `<template>` element's `shadowrootadoptedstylesheets` attribute does not differentiate between specifiers created declaratively (via `<style type="module>`) or external CSS files. This means that the following example is valid:
415+
416+
```html
417+
<my-element>
418+
<template shadowrootmode="open" shadowrootadoptedstylesheets="./foo.css">
419+
...
420+
</template>
421+
</my-element>
422+
```
423+
424+
...where "foo.css" is an external CSS file. Note that `shadowrootadoptedstylesheets` only queries the module map - it doesn't perform a fetch. Developers must instead pre-fetch the CSS file and add it to the module map before the
425+
`<template>` tag is parsed. This can be done imperatively with a Javascript `import` statement within a `<script type="module">`, but requiring script for this scenario is not ideal.
426+
427+
This can be handled declaratively with the existing `<link rel="modulepreload">`, which fetches a module and adds it to the module map.
428+
429+
However, `<link rel="modulepreload">` does not currently work with CSS Module Scripts. This has been proposed by the WHATWG in [Issue 10233](https://github.com/whatwg/html/issues/10233) and makes sense to prioritize to allow external CSS files to work declaratively with `shadowrootadoptedstylesheets`.
430+
431+
This alone does not make `shadowrootadoptedstylesheets` work well with external files, as `<link rel="modulepreload">` does not perform a synchronous fetch, and if the fetch has not completed by the time `shadowrootadoptedstylesheets`, the styles will not be available in the module map.
432+
433+
This scenario could be handled by supporting the `blocking` attribute on `<link rel="modulepreload">`, which should be considered for this feature.
434+
435+
All together, a fully-functional example of using `shadowrootadoptedstylesheets` with an external CSS file would look like this:
436+
437+
```html
438+
<my-element>
439+
<link rel="modulepreload" as="style" href="./foo.css" blocking="render">
440+
<template shadowrootmode="open" shadowrootadoptedstylesheets="./foo.css">
441+
...
442+
</template>
443+
<template shadowrootmode="open" shadowrootadoptedstylesheets="./foo.css">
444+
...
445+
</template>
446+
</my-element>
447+
```
448+
449+
Note that the second `<template>` tag doesn't need a corresponding `<link rel="modulepreload">` - this only needs to happen once per external module, per document, to ensure that it's in the module map before `shadowrootadoptedstylesheets` is parsed.
450+
451+
### Importing Other CSS Files from Within a Declarative Module Script (CSS @import)
452+
453+
Imperative CSS Module Scripts cannot import other CSS Module Scripts. The existinence of a CSS `@import` statement within the text content of an Imperative CSS Module Script fires a script error when imported. Many possible solutions for importing child CSS modules have been discussed in https://github.com/WICG/webcomponents/issues/870, but there is no agreed upon general solution.
454+
455+
Given this existing limitation with `@import` for Imperative CSS Module Scripts, we do not believe this is a blocking issue for Declarative CSS Module Scripts. That said, Declarative CSS Module Scripts provide a new method for creating CSS Modules, which introduces another opportunity for addressing this limitation. This will be investigated as a separate proposal that can be addressed in parallel to this proposal.
456+
457+
Declarative CSS Modules cannot throw script errors when encountering an `@import` statement because script errors can only be thrown in a scripting environment. A reasonable alternative for Declarative CSS Modules is to fail parsing for the module when an `@import` is parsed and log an error in developer tools until a solution for importing nested CSS Modules has been implemented.
458+
446459
### Use with Imperative Module Scripts
447460

448461
Declarative CSS Modules can be used with imperative module scripts from within a static import.
@@ -869,6 +882,8 @@ The following table compares pros and cons of the various proposals:
869882
* Should the `<style>` element be removed from the DOM once it is finished parsing, similar to how the `<template>` element parsing works? This would make the proposed "one-and-done" behaviors more obvious, at the expense of diverging further from existing `<style>` tag behaviors.
870883
* Should the [`media` attribute](https://html.spec.whatwg.org/multipage/semantics.html#attr-style-media) that the `<style>` tag currently supports apply for modules? If so, how should it be applied?
871884
* Does the [`blocking` attribute](https://html.spec.whatwg.org/multipage/semantics.html#attr-style-blocking) on the `<style>` tag apply to CSS Modules? If so, how would it work?
885+
* What happens in scenarios that cross document boundaries, such as `Document.parseHTMLUnsafe`?
886+
* How can developers check for and polyfill `shadowrootadoptedstylesheets`, given that the template element disappears from the DOM?
872887
873888
## References and acknowledgements
874889
Many thanks for valuable feedback and advice from other contributors:

0 commit comments

Comments
 (0)