Shadow DOM (CSS Encapsulation)
The Shadow DOM is one of the three core technologies (alongside Custom Elements and HTML Templates) that make up Web Components. Its primary purpose is to provide robust encapsulation for the internal structure and styling of a component.
In standard CSS, styles are global, meaning a selector can accidentally or intentionally affect any element in the document. The Shadow DOM solves this by creating a hidden, separate DOM tree where CSS selectors only apply to its contents, and external styles cannot easily penetrate.
1. Key Terminology
Understanding the Shadow DOM requires knowing its three main structural parts:
| Term | Definition |
|---|---|
| Shadow Host | The regular DOM element to which the Shadow DOM is attached (e.g., <my-component>). |
| Shadow Tree | The hidden DOM subtree that lives inside the Shadow Host. This contains the component's internal structure and styles. |
| Shadow Boundary | The border between the Shadow Tree and the regular DOM. This boundary enforces encapsulation. |
| Shadow Root | The root node of the Shadow Tree. This is created by calling attachShadow(). |
The Power of Isolation
When CSS is placed inside the Shadow Tree:
- Styles inside the Shadow Tree apply only to elements within that tree.
- External styles from the main document (the Light DOM) stop at the Shadow Boundary and do not affect the elements inside the Shadow Tree.
2. Creating a Shadow Root (JavaScript)
The Shadow DOM is typically created and managed via JavaScript using the attachShadow() method on a DOM element (the Shadow Host).
Mode: open vs. closed
The attachShadow() method requires a configuration object with a mode property:
| Mode | Accessibility | Description |
|---|---|---|
open | Accessible | The Shadow Root can be accessed from outside the component using JavaScript (e.g., element.shadowRoot). This is generally preferred for debugging and flexibility. |
closed | Inaccessible | The Shadow Root cannot be accessed directly from the outside. Styles and structure are fully hidden. |
// Get the host element (a custom element)
const host = document.querySelector('my-component');
// Create the Shadow Root, making it accessible (open)
const shadowRoot = host.attachShadow({ mode: 'open' });
// Add content and styles to the Shadow Tree
shadowRoot.innerHTML = `
<style>
/* This style applies ONLY to the <p> tag inside this Shadow DOM */
p {
color: darkblue;
border: 1px solid blue;
padding: 10px;
}
</style>
<p>Hello from the Shadow DOM!</p>
`;
3. Styling Across the Boundary (The Exceptions)
While the Shadow DOM provides strong isolation, there are mechanisms to allow limited styling customization from the Light DOM.
3.1. Targeting the Host (:host())
The :host pseudo-class allows you to style the Shadow Host element itself from inside the Shadow Tree.
/* Inside the Shadow DOM's <style> tag */
:host {
display: block; /* Make the custom element a block container */
border: 2px solid lightgray;
}
/* :host(.active) styles the host only if it has the 'active' class */
:host(.active) {
border-color: green;
}
3.2. Customizing Internal Parts (::part())
If you want to allow external styles to customize a specific internal element (like a title or a button), you must mark that internal element with the part attribute. You then target it using the ::part() pseudo-element from the Light DOM.
Inside Shadow DOM:
<button part="primary-btn">Click Me</button>
Outside (Light DOM) CSS:
/* Selects the primary-btn part inside any instance of my-component */
my-component::part(primary-btn) {
background-color: purple; /* This style penetrates the boundary */
border-radius: 20px;
}
3.3. Styling Slotted Content (::slotted())
Web Components often use the <slot> element as a placeholder for content passed in from the Light DOM. The ::slotted() pseudo-element lets the Shadow DOM style the slotted content itself.
Inside Shadow DOM's CSS:
/* Style any <h1> elements that are slotted into the component */
::slotted(h1) {
margin-top: 0;
color: orange;
}
Interactive Shadow DOM Demo Structure
Since a fully functional Shadow DOM web component requires a complex setup, this demo illustrates the conceptual structure using a simple Custom Element structure.