Skip to content

Custom Properties and Constructable Stylesheets

CSS custom properties (also called CSS variables) are the primary theming API for web components. Unlike regular CSS rules, custom properties inherit through shadow boundaries. The host element or any ancestor can declare --color: blue, and the shadow root can read it with color: var(--color, defaultValue). The second argument is the fallback, used when the property is not set.

/* Page or host sets the token */
themed-badge {
--badge-bg: #7c3aed;
--badge-color: white;
}
/* Shadow root reads it */
span {
background: var(--badge-bg, #6b7280);
color: var(--badge-color, white);
}

This inheritance is intentional: custom properties were designed to punch through encapsulation. Everything else (selectors, cascade) stops at the shadow boundary, but -- variables flow freely.

Constructable Stylesheets let you create a CSSStyleSheet object in JavaScript, populate it once with .replaceSync(), and then share that single object across many shadow roots by assigning it to shadowRoot.adoptedStyleSheets. Each shadow root that adopts the sheet gets the styles without duplicating the parsed CSS in memory.

const sheet = new CSSStyleSheet();
sheet.replaceSync('span { font-weight: bold; color: steelblue; }');
class MyEl extends HTMLElement {
connectedCallback() {
var shadow = this.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [sheet];
}
}

This is particularly useful in design systems where dozens of components share a common base stylesheet. Parse once, adopt everywhere.

The demo below defines a <themed-badge> element whose background and text color are driven entirely by CSS custom properties set on the host from the page stylesheet. Change the --badge-bg value in the CSS panel to retheme without touching the component code.

Do CSS custom properties (--vars) cross the shadow DOM boundary?
What is the syntax to provide a fallback for a custom property inside a shadow root?
What does adoptedStyleSheets on a shadow root allow?