Table of Contents
A web page may contain tens of thousands of web components. The styles for these components will be specified in a small number of style sheets, perhaps one for each component library.
Most web component uses Shadow DOM. For a style sheet to take effect within the Shadow DOM, it currently must be specified using a <style> element within each shadow root. This can easily have a large time and memory cost if user agents force the style sheet rules to be parsed and stored once for every style element.
Some user agents might attempt to optimize by sharing internal style sheet representations across different instances of the style element. However, component libraries may use JavaScript to modify the style sheet rules, which will thwart style sheet sharing and have large costs in performance and memory.
Often, the main functionality inside the shadow root is provided by a single HTML element that may not itself have children. For a style sheet to also appear inside the shadow root, the shadow root can't be the single HTML element, or a style element, instead it must be a third element that simply contains the style element and the main HTML element that provides the functionality. Thus the shadow root contains three elements when one would otherwise suffice.
Early versions of the Web Component specifications allowed shadow piercing (/deep/, >>> and ::shadow). This allowed document-level style sheets to specify the styles that apply within shadow roots. This avoided the problem of needing a style element within each Shadow DOM, but reduced encapsulation and has been removed from the specifications.
We can provide an API for creating stylesheet objects from script, without needing style elements. Script can optionally add or remove rules from a stylesheet object. Each stylesheet object can be added directly to any number of shadow roots (and/or the top level document), which are in the same document tree where it is constructed on.
// Create style sheets.
let someStyleSheet = new CSSStyleSheet();
someStyleSheet.replaceSync("hr { color: green }");
let anotherStyleSheet = new CSSStyleSheet();
await anotherStyleSheet.replace("@import url('fancystyle.css')");
// Apply style sheet in custom element constructor.
class SomeElement extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: "open"});
shadowRoot.adoptedStyleSheets = [someStyleSheet, anotherStyleSheet];
}
};
// Other example, only filling the sheet with |styleText| when actually needed.
const myElementSheet = new CSSStyleSheet();
class MyElement extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: "open"});
shadowRoot.adoptedStyleSheets = [myElementSheet];
}
connectedCallback() {
// Only actually parse the stylesheet when the first instance is connected.
if (myElementSheet.cssRules.length == 0) {
myElementSheet.replaceSync(styleText);
}
}
}-
We can only construct empty stylesheets, but we can replace the contents of constructed stylesheets with
replace(text)orreplaceSync(text)method, and also modify withinsertRule(rule)anddeleteRule(rule)as well.- Calling
replace(text)on a constructed stylesheet that returns aPromisethat will resolve when all the@importrules intexthad finished loading. - Calling
replaceSync(text)on a constructed stylesheet replaces the content of the stylesheet withtextsynchronously, but it doesn't allow any@importrules - We can't insert
@importrules withinsertRule(rule)to constructed stylesheets. - Example:
// Fine, returns Promise that resolves when 'some.css' finished loading. sheet.replace("@import('some.css');"); // Fails sheet.replaceSync("@import('some.css');"); sheet.insertRule("@import('some.css');");
- Calling
-
Each constructed
CSSStyleSheetis "tied" to theDocumentit is constructed on, meaning that it can only be used in that document tree (whether in a top-level document or shadow trees). If you try to adopt aCSSStyleSheetthat's constructed in a differentDocument, a "NotAllowedError"DOMExceptionwill be thrown.- Example:
<body> <iframe id="someFrame">some frame</iframe> <div id="someDiv">some div</div> </body> <script> let shadowRoot = someDiv.attachShadow({mode: "open"}); let sheet = new CSSStyleSheet(); sheet.replaceSync("* { color: red; })"); // NotAllowedError will be thrown. someFrame.contentDocument.adoptedStyleSheets = [sheet]; </script>
-
After a stylesheet is added to
DocumentOrShadowRoots, changes made to the stylesheet will also reflect in thoseDocumentOrShadowRoots.- Example:
let sheet = new CSSStyleSheet(); sheet.replaceSync("* { color: red; })"); document.adoptedStyleSheets = [sheet]; sheet.insertRule("* { background-color: blue; }"); // Now document will have blue background color as well.
-
Stylesheets added to
adoptedStyleSheetsare part of theDocumentOrShadowRoot's style sheets, and they are ordered after theDocumentOrShadowRoot'sstyleSheets.- This means when there are conflicting rules in the
adoptedStyleSheetsandstyleSheetsand the resolution will consider the order of stylesheets, they treat the sheets inadoptedStyleSheetsas ordered later.
- This means when there are conflicting rules in the