Skip to content

Open vs Closed Mode

attachShadow requires a mode option. There are exactly two values: 'open' and 'closed'. The choice determines whether external JavaScript can access the shadow root through the element.shadowRoot property.

With mode: 'open', the shadow root is exposed via element.shadowRoot:

class OpenBox extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<p>Open shadow root</p>';
}
}
customElements.define('open-box', OpenBox);
var el = document.querySelector('open-box');
console.log(el.shadowRoot); // ShadowRoot { ... }

External scripts, browser DevTools, and testing frameworks can all read and query the shadow root. This is the community standard and what libraries like Lit use.

With mode: 'closed', element.shadowRoot returns null from outside the component:

class ClosedBox extends HTMLElement {
connectedCallback() {
var root = this.attachShadow({ mode: 'closed' });
// store root internally if you still need it
root.innerHTML = '<p>Closed shadow root</p>';
}
}
customElements.define('closed-box', ClosedBox);
var el = document.querySelector('closed-box');
console.log(el.shadowRoot); // null

Note: you must capture the return value of attachShadow in a local variable — this.shadowRoot will be null for closed mode elements accessed from outside.

mode: 'open'mode: 'closed'
element.shadowRootReturns ShadowRootReturns null
DevTools inspectionYesLimited
Testing (querySelector on shadow)EasyHarder
Library support (Lit, etc.)FullRequires workarounds
SecurityNone beyond openMarginal (not a sandbox)

Closed mode does not provide true security. Determined code can still work around it via monkey-patching Element.prototype.attachShadow. Use closed mode only when you have a specific API-hiding reason, not as a security measure.

The demo defines two elements: <open-box> (mode: ‘open’) and <closed-box> (mode: ‘closed’). After they render, a small script reads element.shadowRoot on each and prints the result.

The <pre> output shows [object ShadowRoot] for the open element and null for the closed element.

What does `element.shadowRoot` return when the shadow root was attached with `mode: "closed"`?
Which mode is recommended for components that need to work with browser DevTools and testing frameworks?
When using `mode: "closed"`, how do you keep a reference to the shadow root inside the component?