v148 · CSS · Web Components

CSS Module Playground

Compare three ways to style shadow DOM: dynamic CSSStyleSheet construction, the JS import assertions pattern, and the new declarative approach. Edit CSS live, apply it, watch all shadow roots update.

Checking declarative CSS module script support…

Approach selector

CSS Editor
Shadow Root Preview
// Approach 1: CSSStyleSheet constructed at runtime
const sheet = new CSSStyleSheet();
await sheet.replace(cssText);
shadowRoot.adoptedStyleSheets = [sheet];
Simulated CSS Module
Shadow Root Preview
// Approach 2: JS import assertion (dynamic simulation here)
// In a real module: import styles from './styles.css' assert {type: 'css'};
// Here we simulate via a Blob URL + CSSStyleSheet
const url = URL.createObjectURL(new Blob([cssText], {type:'text/css'}));
const sheet = await import(url, {assert: {type: 'css'}});
shadowRoot.adoptedStyleSheets = [sheet.default];
Declarative Module Editor
Shadow Root Preview
<!-- Chrome 148 declarative approach -->
<style type="module" specifier="my-styles">
  :host { display: block; padding: 1rem; }
  h3 { color: black; }
</style>

<my-card>
  <template shadowrootmode="open"
            shadowrootadoptedstylesheets="my-styles">
    <h3>Card</h3><p>Declarative styling!</p>
  </template>
</my-card>

Load time comparison

CSSStyleSheet
import assert
declarative

Click "Apply" in each tab to record timing. Declarative modules are parsed at HTML-parse time — no JS round-trip overhead.

One module, three shadow roots

Edit the shared CSS below and click "Share to all roots" to watch three independent shadow DOMs update simultaneously — the key value of module adoption.

With declarative modules, all three hosts would have shadowrootadoptedstylesheets="shared-styles" — a single style definition, zero duplication.

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗