class ThemedCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
/* Tries to isolate styles */
.card {
background: var(--card-bg, white); /* CSS var leaks through! */
color: var(--card-text, black);
padding: 16px;
}
</style>
<div class="card"><slot></slot></div>
`;
}
}
// External page sets these β they WILL affect the component
// despite 'closed' shadow root!
// :root { --card-bg: red; --card-text: yellow; }class ThemedCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
/* Documented theming API β CSS vars are intentional leak points */
:host {
/* Define defaults for your theming API */
--card-bg: white;
--card-text: black;
--card-padding: 16px;
}
.card {
/* Use the vars β consumers can override these via :root or :host */
background: var(--card-bg);
color: var(--card-text);
padding: var(--card-padding);
/* Hard-coded styles that consumers CANNOT change: */
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
<div class="card"><slot></slot></div>
`;
}
}
// Usage: --card-bg is your documented theming hook
// Structural styles (radius, shadow) are truly encapsulatedBug: Shadow DOM blocks regular CSS selectors and class names. But CSS custom properties (variables) inherit through shadow boundaries by design β they're meant to be the theming API.
Explanation: CSS custom properties inherit through shadow boundaries β this is intentional. Use this as your theming API: document which variables you support. Hard-code structural styles that consumers shouldn't touch.
Key Insight: Shadow DOM encapsulates regular CSS but CSS custom properties inherit through. This is by design β custom properties ARE the theming API for web components. Embrace this, don't fight it.