Shadows attached

One of the three key ingredients of web components, the shadow DOM provides a way to encapsulate code, in structure, style and functionality.

Root Shadow

The concept sits right at home with web components, but it is possible to experiment with regular HTML elements to illustrate a couple of existential points. And in truth, the specification predates web components, being used in elements such as video.

<video controls width="400">
	<source src="/hi-ho-the-merry-o.webm" type="video/webm" />
</video>

When you add the element in an .html document the resource is presented as part of a complex UI, with additional buttons and controls. These live in the shadow DOM, a separate structure from the rest of the page.

With this in mind, you have the option of entering the shadow DOM yourself with attachShadow, calling the function on a node like a generic div.

const div = document.createElement('div');
div.attachShadow({ mode: 'open' });

document.body.appendChild(div);

The function requires an argument, a mode, and introduces the promised, darker DOM.

Similarly to the regular DOM, you are able to customize the structure in markup. You can, for instance, append an additional element with appendChild, or directly alter the structure through properties like textContent and innerHTML . These operations, however, work on the shadow root, which you are able to access through the shadowRoot property.

div.attachShadow({ mode: 'open' });
div.shadowRoot.innerHTML = `<p>I've got no strings</p>`;

The markup is separate, encapsulated in a subtree, giving you a new scope and close to a reset. Styles you include in the shadows, logic you add to the elements remain relevant to the scope, and don’t cross through the shadow boundary.

There are a few things worth considering, however, starting with the element on which you attach the shadow. Indeed, it is not possible to invoke the function on every HTML element.

The specification has a list of options, of available nodes, and if you consider the number of HTML tags — more than a hundred —, you’ll notice that the collection makes for a restricted club. You don’t have, for instance, the option of attaching a shadow on a video element, and can understand why.

const video = document.querySelector('video');
video.attachShadow({ mode: 'open' });

As the element already has a shadow root, you’ll encounter an error, an uncaught DOMException.

Console Uncaught DOMException Failed to execute 'attachShadow' on 'Element' This element does not support attachShadow

Another issue which deserves more light is mode, the option required in the attachShadow function. You must set a mode to one of two values, a string which can be either "open" or "closed", and while you’ll likely pick the first, it is good to explore the second as well. Going back to the previous snippet, attaching a closed shadow to the generic div prompts an error.

div.attachShadow({ mode: 'closed' });
div.shadowRoot.innerHTML = `<p>I had strings</p>`;

When you update the markup through the shadowRoot property the code breaks with a TypeError.

Console Uncaught TypeError Cannot set properties of null (setting 'innerHTML')

And again for a good reason. When you choose a closed mode, you remove access to the property. Try as you might, the key will resolve to null, and excuse the impossible operation of updating the inexistent markup. By chance, this is what happens with the video element.

div.shadowRoot; // null
video.shadowRoot; // null

Now, there might be a time when you’ll want to restrict access to the shadow DOM, to hide how the element is implemented. You can with the mode.

To update the document on your end, you can exploit the fact that attachShadow returns a reference to the root. Store the reference in a variable, and you can once again operate as normal.

const shadowRoot = div.attachShadow({ mode: 'closed' });
shadowRoot.innerHTML = `<p>But now I'm free</p>`;

But as you weigh your options, and use the shadow DOM with web components, be aware of the imposed restriction. To open the root to end users, and grant the flexibility to affect the document, you always know now how.