Share Compiled CSS with adoptedStyleSheets

The adoptedStyleSheets property is a modern Web API feature that allows developers to apply constructable stylesheets directly to a Document or Shadow DOM root. When building JavaScript Web Components, styling encapsulated shadow roots historically required duplicating <style> tags inside every component instance, which increased memory consumption and degraded performance. By using adoptedStyleSheets, you can instantiate and parse a compiled CSS stylesheet once in memory and share that single reference across multiple custom element instances, ensuring optimal rendering performance and straightforward theme management.

Understanding Constructable Stylesheets and adoptedStyleSheets

adoptedStyleSheets is part of the Constructable Stylesheets specification. It works in tandem with the CSSStyleSheet() constructor, allowing developers to create, modify, and share CSS rules programmatically without needing to insert raw HTML markup or inline <style> tags into the DOM.

Both the Document and ShadowRoot interfaces expose the adoptedStyleSheets property as an array of CSSStyleSheet instances. When a stylesheet is added to this array, the browser applies its rules to the corresponding DOM tree.

The Problem with Traditional Web Component Styling

Shadow DOM boundaries block external styles from penetrating custom elements to ensure encapsulation. Traditionally, developers styled Web Components using two approaches:

  1. Inline <style> tags: Placing a <style> element inside the template or shadowRoot of every instance.
  2. External <link> tags: Referencing an external stylesheet within each component’s shadow root.

Both patterns have severe performance drawbacks. When thousands of component instances are rendered, the browser must parse and store identical CSS text repeatedly, leading to high memory overhead, slower initial render times, and increased layout recalculations.

How to Share Compiled CSS Using adoptedStyleSheets

Modern build pipelines (such as Webpack, Vite, or Rollup) can compile Sass, Less, PostCSS, or Tailwind styles into a single CSS string. This compiled CSS can then be turned into a shared CSSStyleSheet instance and attached to any number of components.

Step 1: Create and Populate the Stylesheet

First, create a new CSSStyleSheet object and populate it using either replace() (asynchronous) or replaceSync() (synchronous):

// Compiled CSS imported from a build tool or defined as a string
const compiledStyles = `
  :host {
    display: inline-block;
    font-family: system-ui, sans-serif;
  }
  .btn {
    background-color: #0066cc;
    color: #ffffff;
    padding: 8px 16px;
    border-radius: 4px;
    border: none;
    cursor: pointer;
  }
  .btn:hover {
    background-color: #0052a3;
  }
`;

// Instantiate the shared stylesheet
const sharedSheet = new CSSStyleSheet();
sharedSheet.replaceSync(compiledStyles);

export default sharedSheet;

Step 2: Adopt the Stylesheet Across Components

Import the shared sheet and assign it to the adoptedStyleSheets property of the element’s shadowRoot:

import sharedSheet from './shared-styles.js';

class CustomButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Share the compiled stylesheet reference
    shadow.adoptedStyleSheets = [sharedSheet];
    
    shadow.innerHTML = `<button class="btn"><slot></slot></button>`;
  }
}

customElements.define('custom-button', CustomButton);

If another custom element needs the same base styles along with its own unique styles, it can combine multiple sheets:

import baseSheet from './shared-styles.js';

const componentSheet = new CSSStyleSheet();
componentSheet.replaceSync(`.btn { border: 2px solid black; }`);

class OutlinedButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Combine base styles with component-specific styles
    shadow.adoptedStyleSheets = [baseSheet, componentSheet];
    
    shadow.innerHTML = `<button class="btn"><slot></slot></button>`;
  }
}

customElements.define('outlined-button', OutlinedButton);

Key Advantages of Using adoptedStyleSheets