Shadow DOM: `open` vs `closed`

Emilio Martinez
3 min readJul 9, 2018

--

The Web Component spec is made up of four pillars: HTML Templates, Custom elements, Shadow DOM, and HTML Imports, although that last one hasn’t gotten traction among the standards committees at all.

As of v1 of the spec, the current version, the API to create a ShadowRoot changed to add a configuration object that determines the encapsulation mode that shadow tree will have upon creation. The ShadowRoot created by v0’s createShadowRoot can be considered open; closed is new to v1.

///// v0 API /////const e = document.createElement('div');
const shadowRoot = e.createShadowRoot();
///// v1 API /////const e = document.createElement('div');
const shadowRoot = e.attachShadow({ mode: 'open' });
// or for a closed Shadow DOM
const e = document.createElement('div');
const shadowRoot = e.attachShadow({ mode: 'closed' });

While plenty is generally said and written about the Shadow DOM spec overall, this encapsulation mode is not commented on much, so what does it get us?

TL;DR

The short answer is… not a ton!

While there are certainly differences, when in doubt, go open. Opting for closed will mostly limit public access to the component’s shadow tree; however, that could come at the cost of flexibility for your component’s end users. If you don’t have a strong reason for it, go open.

Closed mode

Let’s dive deeper into what closed mode means for your component design.

If you’ve ever opted-in to see the user-agent’s Shadow DOM, perhaps you’ve seen the underlying shadow tree within a <video> element. You may notice, however, that there’s no Javascript access to that shadow tree. That is essentially what closed mode controls: access.

Video user-agent Shadow DOM in Chrome

The design goal of Shadow DOM’s closed mode is to “disallow” public access to the nodes within a closed shadow tree, and because encapsulation is one of Shadow DOM’s main goals, a closed shadow tree in many ways makes sense. However, as mentioned above, you don’t actually get a ton.

Let’s look at a demo, and I’ll explain further below.

Shadow DOM`open` and`closed` mode demo

In the demo above, both components showcases—demonstrating open and closed, respectively— are intentionally created to be almost identical in their underlying structure to the point that their one and only difference is their encapsulation mode. Notice how for all intents an purposes you’re largely getting the same Shadow DOM benefits: an isolated DOM, scoped CSS, and a declarative, markup-based API.

What changes is how we handle the ShadowRoot upon creation:

class MyComponent extends HTMLElement {
constructor () {
super();
// With closed, a ShadowRoot reference needs to be stored
this._shadowRoot = this.attachShadow({ mode: 'closed' });
}
connectedCallback() {
// Do stuff with ShadowRoot via stored reference
}
}
// Define component in custom element registry
window.customElements.define('my-component', MyComponent);
// Create component
const el = document.createElement('my-component');
document.body.appendChild(el);
// ShadowRoot is not publicly available
console.log(el.shadowRoot); // => null

Because Element.shadowRoot’s public access is disallowed, it’s commonly perceived as a “security” feature. However, because it requires a stored reference, it’s not actually all that secure.

// Continuing the above example
console.log(el._shadowRoot); // => ShadowRoot {}

The alternative perhaps would be to store the ShadowRoot references in a WeakMap; however, that would not prevent an attacker from overriding Element.prototype.attachShadow.

“If I can find ways to access the `ShadowRoot` anyway, then why not jut use `closed`?”

Glad you asked.

Ultimately, it’s up to you. However, keep in mind that closed mode will make your components less flexible for end users. Not providing this access might result in users forking, creating their own, or simply finding another component that perhaps better suits their needs.

One last note

There are a couple other APIs that are impacted by closed mode to be mindful of:

Those are arguably not super common APIs as far as Shadow DOM goes, but it’s definitely worth noting regardless. The documentation pages linked to above contain examples of those respective cases.

--

--

Emilio Martinez

It just occurred to me I could write my thoughts once in a while.