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
- Shadow Host: The regular DOM node in the Light DOM that the Shadow DOM is attached to.
- Shadow Tree: The hidden DOM sub-tree rendered inside the Shadow Host.
- Shadow Root: The root node of the Shadow Tree, serving as the boundary point.
- Shadow Boundary: The invisible separation barrier between the Shadow DOM and the Light DOM.
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:
- 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. - 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
colorandfont-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:
mode: 'open': TheshadowRootproperty on the host element is publicly accessible via JavaScript (element.shadowRoot). This is the standard choice for most modern component architectures.mode: 'closed': TheshadowRootproperty returnsnullwhen accessed from the outside, blocking external JavaScript access unless an internal reference is explicitly preserved.
Crossing the Boundary: Controlled Styling
While isolation is strict, the Shadow DOM provides mechanisms for deliberate external communication and theming:
- CSS Custom Properties (Variables): CSS variables cross shadow boundaries automatically, allowing global themes to style inner components without breaking encapsulation.
:hostSelector: Used inside the Shadow DOM to style the Shadow Host element itself.::part()Selector: Allows component authors to explicitly expose specific internal elements for external styling from global CSS.