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.
Approach selector
// Approach 1: CSSStyleSheet constructed at runtime const sheet = new CSSStyleSheet(); await sheet.replace(cssText); shadowRoot.adoptedStyleSheets = [sheet];
// 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];
<!-- 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
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 ↗