Shadow DOM: Guide to DOM and Style Isolation

The Shadow DOM is a browser-native Web Components standard that enables developers to build completely self-contained components by encapsulating markup, behavior, and styling. By creating an isolated sub-tree attached to a standard DOM element, the Shadow DOM prevents external styles from leaking in and internal styles from leaking out, while also protecting the component’s internal elements from global JavaScript DOM queries.

Understanding the Shadow DOM

In standard web development, the Document Object Model (DOM) is a single, global tree structure. Any CSS rule written in a global stylesheet can affect any matching element, and JavaScript queries like document.querySelector() search the entire page.

The Shadow DOM introduces a private, scoped DOM sub-tree attached to an element, shielding internal implementations from the main document (often called the Light DOM).

Key Terminology

How the Shadow DOM Isolates the DOM Tree

When a Shadow Root is attached to an element, the elements inside the Shadow Tree become invisible to the global document API.

If a component inside a Shadow DOM contains a <button class="btn">, running document.querySelector('.btn') in the global scope returns null (or only buttons that exist in the Light DOM). To interact with elements inside a Shadow Tree, access must be explicitly granted through the component’s Shadow Root:

// Selecting the host element from the Light DOM
const hostElement = document.querySelector('#custom-card');

// Accessing elements inside the Shadow Tree
const internalButton = hostElement.shadowRoot.querySelector('.btn');

This structural encapsulation ensures that complex component internals cannot be accidentally modified, overwritten, or broken by external scripts.

How the Shadow DOM Isolates Styles

Style leakage is one of the most common challenges in web development. The Shadow DOM resolves this through strict CSS encapsulation:

  1. Internal Styles Stay Inside: A <style> tag placed inside a Shadow Root applies strictly to the elements within that specific Shadow Tree. It will not leak out to the parent page.
  2. External Styles Stay Outside: Global CSS selectors defined in external stylesheets do not cascade into the Shadow Tree, with the exception of inherited properties (such as color and font-family).

Example Implementation

class CustomAlert extends HTMLElement {
  constructor() {
    super();

    // 1. Attach a shadow root to the custom element
    const shadow = this.attachShadow({ mode: 'open' });

    // 2. Define scoped markup and styling
    shadow.innerHTML = `
      <style>
        /* Scoped strictly to this component */
        p {
          color: white;
          background-color: crimson;
          padding: 12px;
          border-radius: 4px;
        }
      </style>
      <p>This is an isolated alert box.</p>
    `;
  }
}

// Register the custom element
customElements.define('custom-alert', CustomAlert);

In this example, the rule targeting <p> will not alter any paragraph elements in the main document, nor will global <p> styles override the alert’s internal appearance.

Open vs. Closed Shadow DOM Modes

When invoking attachShadow({ mode: '...' }), developers can choose between two modes:

Crossing the Boundary: Controlled Styling

While isolation is strict, the Shadow DOM provides mechanisms for deliberate external communication and theming: