- 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.