A custom element alone does not protect its styles: a page rule such as p { color: red } also reaches inside the component. Shadow DOM gives a component its own isolated DOM and CSS, while <template> and <slot> make templating and content passing easy.

Shadow DOM style boundary
Shadow DOM style boundary

Shadow DOM: host, shadow root, light DOM

  • host — the element that carries the shadow root;
  • shadow root — an isolated subtree;
  • light DOM — the children you write inside the host.
js
class FancyCard extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    root.innerHTML = `
      <style>p { color: crimson; }</style>
      <p>Inside the shadow</p>`;
  }
}
customElements.define('fancy-card', FancyCard);

const card = document.createElement('fancy-card');
document.body.append(card);

console.log(card.shadowRoot.mode);                            // open
console.log(card.shadowRoot.querySelector('p').textContent);  // Inside the shadow
console.log(document.querySelector('p'));                     // null

⚠️ mode: 'closed' gives no real security (and makes tests and browser extensions harder). Most components choose open.

What crosses the boundary