Skip to content

Adopted Stylesheets

This lesson explains Adopted Stylesheets with clear examples, use cases, and best practices so you can build reusable Web Components confidently.

Web Components Adopted Stylesheets Overview

Adopted Stylesheets allow Web Components to share reusable CSS styles efficiently across multiple Shadow DOM roots using CSSStyleSheet and adoptedStyleSheets.

Instead of injecting a new <style> tag into every component instance, a constructable stylesheet can be created once and adopted by many components. This improves performance, reduces duplicate CSS, and makes design system styling easier to manage.

Adopted Stylesheets List

  • 1. Create a Constructable Stylesheet.
    A constructable stylesheet is created with new CSSStyleSheet(). It can be reused across multiple Shadow DOM roots.
    const sheet =
      new CSSStyleSheet();
    
    sheet.replaceSync(
      `
        :host {
          display: block;
          font-family: system-ui, sans-serif;
        }
      `
    );
  • 2. Adopt the stylesheet inside Shadow DOM.
    Assign the stylesheet to shadowRoot.adoptedStyleSheets.
    class MyCard extends HTMLElement {
      constructor() {
        super();
    
        const shadow =
          this.attachShadow(
            { mode: "open" }
          );
    
        shadow.adoptedStyleSheets =
          [sheet];
    
        shadow.innerHTML =
          `
            <article>
              <slot></slot>
            </article>
          `;
      }
    }
    
    customElements.define(
      "my-card",
      MyCard
    );
  • 3. Reuse one stylesheet across many components.
    The same stylesheet object can be shared by several component classes or many instances of the same component.
    const sharedSheet =
      new CSSStyleSheet();
    
    sharedSheet.replaceSync(
      `
        :host {
          box-sizing: border-box;
        }
    
        * {
          box-sizing: inherit;
        }
      `
    );
  • 4. Use replaceSync() for synchronous CSS.
    replaceSync() immediately replaces stylesheet content. It is useful when CSS is already available as a string.
    sheet.replaceSync(
      `
        button {
          border: 1px solid currentColor;
          border-radius: 0.5rem;
          padding: 0.5rem 0.75rem;
        }
      `
    );
  • 5. Use replace() for asynchronous CSS updates.
    replace() returns a Promise and can be used when stylesheet updates should be handled asynchronously.
    async function loadStyles() {
      const sheet =
        new CSSStyleSheet();
    
      await sheet.replace(
        `
          :host {
            display: block;
          }
        `
      );
    
      return sheet;
    }
  • 6. Combine multiple adopted stylesheets.
    A Shadow Root can adopt multiple stylesheets. This is useful for base styles, theme styles, and component-specific styles.
    shadow.adoptedStyleSheets =
      [
        resetSheet,
        themeSheet,
        componentSheet
      ];
  • 7. Create base styles for all components.
    Shared base styles can normalize box sizing, typography, focus styles, and common layout rules.
    export const baseSheet =
      new CSSStyleSheet();
    
    baseSheet.replaceSync(
      `
        :host {
          box-sizing: border-box;
          font-family: system-ui, sans-serif;
        }
    
        *, *::before, *::after {
          box-sizing: inherit;
        }
      `
    );
  • 8. Create component-specific styles.
    Each component can have its own stylesheet while still sharing base and theme sheets.
    const cardSheet =
      new CSSStyleSheet();
    
    cardSheet.replaceSync(
      `
        article {
          border: 1px solid #dbe5f1;
          border-radius: 0.75rem;
          padding: 1rem;
        }
      `
    );
  • 9. Use CSS custom properties for theming.
    CSS variables can pass theme values from the page into Shadow DOM.
    const themeSheet =
      new CSSStyleSheet();
    
    themeSheet.replaceSync(
      `
        :host {
          color: var(--wc-color-text, #111827);
          background: var(--wc-color-bg, #ffffff);
        }
      `
    );
  • 10. Update shared styles globally.
    Updating one shared stylesheet can affect every Shadow Root that adopted it.
    themeSheet.replaceSync(
      `
        :host {
          color: var(--theme-text, #111827);
          background: var(--theme-bg, #ffffff);
        }
      `
    );
  • 11. Use adopted stylesheets in a design system.
    Design systems can export shared sheets for tokens, reset styles, typography, spacing, and focus states.
    export const focusSheet =
      new CSSStyleSheet();
    
    focusSheet.replaceSync(
      `
        :focus-visible {
          outline: 3px solid var(--focus-color, currentColor);
          outline-offset: 3px;
        }
      `
    );
  • 12. Share tokens across components.
    Tokens can be expressed as CSS custom properties inside a shared stylesheet.
    export const tokensSheet =
      new CSSStyleSheet();
    
    tokensSheet.replaceSync(
      `
        :host {
          --space-xs: 0.25rem;
          --space-sm: 0.5rem;
          --space-md: 1rem;
          --radius-md: 0.75rem;
        }
      `
    );
  • 13. Use styles with Shadow DOM encapsulation.
    Adopted stylesheets apply inside the Shadow Root and do not leak to the main document.
    shadow.adoptedStyleSheets =
      [componentSheet];
    
    shadow.innerHTML =
      `
        <button type="button">
          Save
        </button>
      `;
  • 14. Style the host element.
    Use :host to style the custom element itself.
    sheet.replaceSync(
      `
        :host {
          display: inline-block;
        }
    
        :host([disabled]) {
          opacity: 0.6;
          pointer-events: none;
        }
      `
    );
  • 15. Style host variants.
    Attribute selectors with :host() are useful for variants such as size, tone, disabled, selected, and expanded.
    sheet.replaceSync(
      `
        :host([variant="primary"]) button {
          font-weight: 700;
        }
    
        :host([size="small"]) button {
          padding: 0.25rem 0.5rem;
        }
      `
    );
  • 16. Style slotted content carefully.
    Use ::slotted() to style assigned light DOM children, but remember it only targets the slotted element itself.
    sheet.replaceSync(
      `
        ::slotted(h2) {
          margin-top: 0;
          font-size: 1.25rem;
        }
      `
    );
  • 17. Use CSS parts for external styling hooks.
    Expose stable styling hooks with part and allow consumers to style them with ::part().
    <my-card></my-card>
    
    <style>
      my-card::part(title) {
        font-weight: 700;
      }
    </style>
    shadow.innerHTML =
      `
        <h2 part="title">
          <slot name="title"></slot>
        </h2>
      `;
  • 18. Add stylesheets after preserving existing sheets.
    When adding a new stylesheet, include existing adopted stylesheets so they are not overwritten.
    shadow.adoptedStyleSheets =
      [
        ...shadow.adoptedStyleSheets,
        extraSheet
      ];
  • 19. Avoid creating stylesheets inside every instance when shared styles are enough.
    Create shared sheets once outside the class to reduce repeated work.
    const buttonSheet =
      new CSSStyleSheet();
    
    buttonSheet.replaceSync(
      `
        button {
          cursor: pointer;
        }
      `
    );
    
    class MyButton extends HTMLElement {
      constructor() {
        super();
    
        const shadow =
          this.attachShadow(
            { mode: "open" }
          );
    
        shadow.adoptedStyleSheets =
          [buttonSheet];
      }
    }
  • 20. Use feature detection for compatibility.
    Some older environments may not support constructable stylesheets. Provide a fallback when needed.
    const supportsAdoptedSheets =
      "adoptedStyleSheets" in Document.prototype &&
      "replaceSync" in CSSStyleSheet.prototype;
    
    console.log(supportsAdoptedSheets);
  • 21. Provide a fallback using <style>.
    If adopted stylesheets are not supported, inject a normal style element into the Shadow Root.
    function applyStyles(shadow, cssText) {
      if ("adoptedStyleSheets" in shadow) {
        const sheet =
          new CSSStyleSheet();
    
        sheet.replaceSync(cssText);
    
        shadow.adoptedStyleSheets =
          [sheet];
    
        return;
      }
    
      const style =
        document.createElement("style");
    
      style.textContent =
        cssText;
    
      shadow.appendChild(style);
    }
  • 22. Keep CSS strings readable.
    Store long CSS in constants or separate modules instead of mixing large style strings directly inside component constructors.
    const cssText =
      `
        :host {
          display: block;
        }
    
        .content {
          padding: 1rem;
        }
      `;
  • 23. Export reusable styles from a module.
    Shared styles can be imported wherever Web Components need them.
    // styles.js
    
    export const cardSheet =
      new CSSStyleSheet();
    
    cardSheet.replaceSync(
      `
        article {
          border: 1px solid #dbe5f1;
          border-radius: 0.75rem;
        }
      `
    );
    // my-card.js
    
    import {
      cardSheet
    } from "./styles.js";
  • 24. Use adopted stylesheets with multiple Shadow Roots.
    The same stylesheet can be assigned to many Shadow Roots for consistent component styling.
    class MyBadge extends HTMLElement {
      constructor() {
        super();
    
        const shadow =
          this.attachShadow(
            { mode: "open" }
          );
    
        shadow.adoptedStyleSheets =
          [tokensSheet, badgeSheet];
    
        shadow.innerHTML =
          `<span><slot></slot></span>`;
      }
    }
  • 25. Use document adopted stylesheets for page-level styles.
    document.adoptedStyleSheets can apply constructable stylesheets to the main document.
    const pageSheet =
      new CSSStyleSheet();
    
    pageSheet.replaceSync(
      `
        body {
          margin: 0;
          font-family: system-ui, sans-serif;
        }
      `
    );
    
    document.adoptedStyleSheets =
      [
        ...document.adoptedStyleSheets,
        pageSheet
      ];
  • 26. Avoid unsafe user-generated CSS.
    Do not place untrusted user input directly into stylesheet strings. User-controlled CSS can create visual spoofing, data exposure risks, or broken UI.
    function setThemeColor(color) {
      if (!/^#[0-9a-fA-F]{6}$/.test(color)) {
        return;
      }
    
      host.style.setProperty(
        "--theme-color",
        color
      );
    }
  • 27. Keep accessibility styles in shared sheets.
    Focus styles, forced colors, reduced motion, and disabled styles should be consistent across components.
    const accessibilitySheet =
      new CSSStyleSheet();
    
    accessibilitySheet.replaceSync(
      `
        :focus-visible {
          outline: 3px solid currentColor;
          outline-offset: 3px;
        }
    
        @media (prefers-reduced-motion: reduce) {
          * {
            animation-duration: 0.01ms;
            transition-duration: 0.01ms;
          }
        }
      `
    );
  • 28. Test style updates across all component instances.
    Because shared sheets update every adopter, test that global style updates do not unexpectedly break other components.
  • 29. Avoid overusing global adopted stylesheets.
    Adopted stylesheets are powerful, but component styles should remain scoped and predictable. Use global document sheets only for true global rules.
  • 30. Use adopted stylesheets for performance-sensitive component libraries.
    They are especially useful in design systems where many component instances share the same base styles, tokens, and accessibility rules.

Complete Adopted Stylesheet Component Example

This example creates reusable base styles, component styles, and a Web Component that adopts both stylesheets.

const baseSheet =
  new CSSStyleSheet();

baseSheet.replaceSync(
  `
    :host {
      box-sizing: border-box;
      font-family: system-ui, sans-serif;
    }

    *, *::before, *::after {
      box-sizing: inherit;
    }
  `
);

const cardSheet =
  new CSSStyleSheet();

cardSheet.replaceSync(
  `
    article {
      border: 1px solid var(--card-border, #dbe5f1);
      border-radius: 0.75rem;
      padding: 1rem;
      background: var(--card-bg, #ffffff);
    }

    ::slotted([slot="title"]) {
      margin-top: 0;
    }
  `
);

class MyCard extends HTMLElement {
  constructor() {
    super();

    const shadow =
      this.attachShadow(
        { mode: "open" }
      );

    shadow.adoptedStyleSheets =
      [
        baseSheet,
        cardSheet
      ];

    shadow.innerHTML =
      `
        <article part="card">
          <slot name="title"></slot>
          <slot></slot>
        </article>
      `;
  }
}

customElements.define(
  "my-card",
  MyCard
);
<my-card>
  <h2 slot="title">
    Account Summary
  </h2>

  <p>
    View billing and subscription details.
  </p>
</my-card>

Reusable Fallback Helper

This helper uses adopted stylesheets when supported and falls back to a normal <style> tag when needed.

function applyComponentStyles(shadow, sheets, cssText) {
  if (
    "adoptedStyleSheets" in shadow &&
    "replaceSync" in CSSStyleSheet.prototype
  ) {
    shadow.adoptedStyleSheets =
      sheets;

    return;
  }

  const style =
    document.createElement("style");

  style.textContent =
    cssText;

  shadow.appendChild(style);
}

Adopted Stylesheets vs Style Tags

Feature Adopted Stylesheets Style Tags
Reuse One stylesheet can be shared. Each component usually has its own style tag.
Performance Efficient for many component instances. Can duplicate CSS repeatedly.
Updates Shared sheet updates all adopters. Each style tag must be updated separately.
Compatibility Modern browser feature. Works broadly.
Best Use Design systems and reusable components. Simple fallback or small components.

Common Adopted Stylesheet Mistakes

  • Creating a new stylesheet for every component instance unnecessarily.
  • Overwriting existing adoptedStyleSheets instead of preserving them.
  • Forgetting browser support and not providing a fallback.
  • Putting untrusted user input directly into CSS strings.
  • Expecting ::slotted() to style deep children.
  • Using global document sheets for component-specific styles.
  • Forgetting that updating a shared sheet affects all adopters.
  • Making CSS strings too large and hard to maintain.
  • Not testing focus, high contrast, and reduced motion styles.
  • Mixing many unrelated style responsibilities into one stylesheet.

Adopted Stylesheets Best Practices

  • Create shared stylesheets once and reuse them.
  • Split styles into base, tokens, accessibility, theme, and component sheets.
  • Use CSS custom properties for theming.
  • Preserve existing adopted stylesheets when adding new ones.
  • Provide a <style> fallback when required.
  • Keep stylesheet modules focused and reusable.
  • Avoid injecting untrusted CSS.
  • Use :host, ::slotted(), and ::part() intentionally.
  • Test style changes across all components that share a sheet.
  • Use adopted stylesheets for reusable component libraries and design systems.

Pro Tip

Adopted stylesheets are especially valuable in Web Component design systems. Create shared sheets for tokens, reset styles, focus styles, and themes, then combine them with component-specific sheets for scalable and efficient styling.