Seamless reusable styles.
Constructable Stylesheets are a way to create and distribute reusable styles when using Shadow DOM.
It has always been possible to create stylesheets using JavaScript. However, the
process has historically been to create a <style>
element using
document.createElement('style')
, and then access its sheet property to obtain
a reference to the underlying
CSSStyleSheet
instance. This method can produce duplicate CSS code and its attendant bloat,
and the act of attaching leads to a flash of unstyled content whether there is
bloat or not. The CSSStyleSheet
interface is the root of a collection of CSS
representation interfaces referred to as the
CSSOM,
offering a programmatic way to manipulate stylesheets as well as eliminating the
problems associated with the old method.
Constructable Stylesheets make it possible to define and prepare shared CSS styles, and then apply those styles to multiple Shadow Roots or the Document easily and without duplication. Updates to a shared CSSStyleSheet are applied to all roots into which it has been adopted, and adopting a stylesheet is fast and synchronous once the sheet has been loaded.
The association set up by Constructable Stylesheets lends itself well to a
number of different applications. It can be used to provide a centralized theme
used by many components: the theme can be a CSSStyleSheet
instance passed to
components, with updates to the theme propagating out to components
automatically. It can be used to distribute CSS Custom
Property values to
specific DOM subtrees without relying on the
cascade. It can even
be used as a direct interface to the browser's CSS parser, making it easy to
preload stylesheets without injecting them into the DOM.
Constructing a stylesheet
Rather than introducing a new API to accomplish this, the Constructable
StyleSheets
specification makes it possible to create stylesheets imperatively by invoking
the CSSStyleSheet()
constructor. The resulting CSSStyleSheet object has two
new methods that make it safer to add and update stylesheet rules without
triggering Flash of Unstyled
Content (FOUC).
The
replace()
and
replaceSync()
methods both replace the stylesheet with a string of CSS, and replace()
returns a Promise. In both cases, external stylesheet references are not
supported—any @import
rules are ignored and will produce a warning.
const sheet = new CSSStyleSheet();
// replace all styles synchronously:
sheet.replaceSync('a { color: red; }');
// replace all styles:
sheet.replace('a { color: blue; }')
.then(() => {
console.log('Styles replaced');
})
.catch(err => {
console.error('Failed to replace styles:', err);
});
// Any @import rules are ignored.
// Both of these still apply the a{} style:
sheet.replaceSync('@import url("styles.css"); a { color: red; }');
sheet.replace('@import url("styles.css"); a { color: red; }');
// Console warning: "@import rules are not allowed here..."
Using constructed stylesheets
The second new feature introduced by Constructable StyleSheets is an
adoptedStyleSheets
property available on Shadow
Roots
and Documents. This
lets us explicitly apply the styles defined by a CSSStyleSheet
to a given DOM
subtree. To do so, we set the property to an array of one or more stylesheets to
apply to that element.
// Create our shared stylesheet:
const sheet = new CSSStyleSheet();
sheet.replaceSync('a { color: red; }');
// Apply the stylesheet to a document:
document.adoptedStyleSheets.push(sheet);
// Apply the stylesheet to a Shadow Root:
const node = document.createElement('div');
const shadow = node.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets.push(sheet);
Putting it all together
With Constructable StyleSheets, web developers now have an explicit solution for creating CSS StyleSheets and applying them to DOM trees. We have a new Promise-based API for loading StyleSheets from a string of CSS source that uses the browser's built-in parser and loading semantics. Finally, we have a mechanism for applying stylesheet updates to all usages of a StyleSheet, simplifying things like theme changes and color preferences.
Looking ahead
The initial version of Constructable Stylesheets shipped with the API
described here, but there's work underway to make things easier to use. There's
a proposal to extend
the adoptedStyleSheets
FrozenArray with dedicated methods for inserting and
removing stylesheets, which would obviate the need for array cloning and avoid
potential duplicate stylesheet references.