Open vs Closed Shadow DOM in JavaScript
Shadow DOM allows developers to encapsulate CSS and HTML structure
within web components, preventing styles and DOM queries from leaking
into or out of the component. When creating a shadow root using
element.attachShadow({ mode: '...' }), JavaScript requires
you to specify the encapsulation mode as either open or
closed. This article explores the functional differences,
access mechanisms, and common use cases for both open and
closed Shadow DOM modes.
Understanding Shadow DOM Modes
The mode of a Shadow DOM root controls how accessible the internal DOM tree is to JavaScript running in the outer document. It is defined at initialization:
// Open mode
const openShadow = element.attachShadow({ mode: 'open' });
// Closed mode
const closedShadow = element.attachShadow({ mode: 'closed' });Open Mode Shadow DOM
When a shadow root is set to mode: 'open', the
component’s internal DOM remains accessible from external JavaScript via
the host element’s .shadowRoot property.
- External Access: Calling
element.shadowRootreturns the ShadowRoot object. - Querying Internals: External scripts can query and
manipulate internal nodes using methods like
element.shadowRoot.querySelector(). - Testing & Extensibility: Open mode makes it easy to write unit tests, automate user actions, and allow consumers of custom elements to inspect or extend internal structures when strictly necessary.
Example:
const customElem = document.querySelector('my-open-element');
console.log(customElem.shadowRoot); // Returns the ShadowRoot object
const internalBtn = customElem.shadowRoot.querySelector('button'); // AccessibleClosed Mode Shadow DOM
When a shadow root is set to mode: 'closed', external
JavaScript cannot access the shadow root through the host element.
- External Access: Calling
element.shadowRootreturnsnull. - Internal Reference: The internal DOM can only be
referenced if the developer explicitly stores the reference returned by
attachShadow()inside a private variable or closure within the component’s scope. - Limited Tooling: External testing utilities, accessibility tools, and browser extensions may struggle to interact with closed shadow trees.
Example:
const customElem = document.querySelector('my-closed-element');
console.log(customElem.shadowRoot); // Returns nullIs Closed Mode a Security Boundary?
Closed mode is not a security feature. It is designed to provide
encapsulation and prevent accidental interference, not malicious access.
External code can still bypass closed mode by monkey-patching
Element.prototype.attachShadow before the component
initializes:
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
const shadowRoot = originalAttachShadow.call(this, init);
console.log('Intercepted closed root:', shadowRoot);
return shadowRoot;
};Summary of Differences
| Feature | Open Mode | Closed Mode |
|---|---|---|
element.shadowRoot |
Returns ShadowRoot |
Returns null |
| External DOM Access | Allowed | Disallowed (without internal references) |
| Testability | High | Requires specific architectural workarounds |
| Style Encapsulation | Complete (CSS stays isolated) | Complete (CSS stays isolated) |
| Security | Not a security boundary | Not a security boundary |
Best Practices
In almost all standard web development scenarios,
mode: 'open' is recommended. It delivers the style and
markup encapsulation benefits of Web Components while maintaining the
transparency required for testing, accessibility tools, and debugging.
mode: 'closed' should generally be reserved for native
browser controls (such as <video> or
<input>) or specialized widgets requiring strict
internal state isolation.