Shadow DOM Implementation in Javascript

Vinay Mittal
Frontend Weekly
Published in
6 min readDec 1, 2022

--

Photo by Clint Adair on Unsplash

An important aspect of web components is encapsulation — with shadow Dom we can make markup structure, style, and its behaviour hidden and separate from other code on the page so that different parts do not clash. The Shadow DOM API is a key part of this, providing a way to attach a hidden separated DOM to an element.

In this article we will discuss about:-

UseCases of shadow DOM:-

  • Let say some dynamic html content coming in api and that would be visible on same page, so in that case we can add that html content as a shadow root in DOM to make it visible. As that html content have their own style and it should not be leaked to regular DOM.
  • Shadow DOM can also be feasible in case when different team works on different elements that are rendered on the same page. And these elements have their own independent functionality. Using Shadow DOM to render them would allow us to include them into the DOM and prevent any unexpected leakage of styles or functionality without extra alignment among the teams.
  • Mainly shadow DOM uses when we want to insert some dynamic html content into an application and want to make sure that no styles or functionality will collide.

Structure of shadow Dom in regular DOM tree:-

Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree — this shadow DOM tree starts with a shadow root, underneath which you can attach any element, in the same way as the normal DOM.

Image from https://developer.mozilla.org/

Here are some terminology of shadow DOM :

  • Shadow host: The regular DOM node that the shadow DOM is attached to.
  • Shadow tree: The DOM tree inside the shadow DOM.
  • Shadow boundary: the place where the shadow DOM ends, and the regular DOM begins.
  • Shadow root: The root node of the shadow tree.

Creating shadow Dom:-

To create shadow DOM for an element, call element.attachShadow() , which takes object as parameter with mode as key and value can be open or closed and closed by default.

const shadowOpen = elementRef.attachShadow({ mode: "open" });
const shadowClosed = elementRef.attachShadow({ mode: "closed" });

The mode closed means if we want to access the shadow Dom with javascript it will return null and can’t access that, and open means we can access the shadow Dom and it’s child nodes with Javascript like:-

const myCustomElement = document.querySelector("element-details");
const childNodes = Array.from(myCustomElement.shadowRoot.childNodes);
open and closed shadow Dom mode Example

Before jump in to the example let’s understand template and window.customElements first.

Template and customElements:-

Template:-

The <template> HTML element is a mechanism for holding html that is not to be rendered immediately when a page is loaded but may be instantiated subsequently during runtime using JavaScript.

<template id="my-paragraph">
<p>My paragraph</p>
</template>

This won’t appear in your page until you grab a reference to it with JavaScript and then append it to the DOM, using something like the following:-

let template = document.getElementById("my-paragraph");
let templateContent = template.content;
document.body.appendChild(templateContent);

So, we have to get the template content first using .content and append back to the DOM to make it visible.

customElements:-

The customElements exists in window object and is used for defining a custom element and teaching the browser about a new tag. In the customElements, methods are exists of typeCustomElementRegistry
The CustomElementRegistry.define() method can be used to create the new custom element. We are using .define() method to create custom element in below example.

The .define() takes below arguments:-

- first: A string representing the name you are giving to the element (kebab-case).
- second: A class object that defines the behaviour of the element.
- third: is optional and an object with key as extends and value which specifies the built-in element your element inherits from.

customElements.define("word-count", WordCount, { extends: "p" });

Implementation of shadow DOM:-

Let’s create index.html file which contains template tag with content:-

define a customElement and attach template content in shadow root like:-

Here we are creating a custom element i.e. my-custom-element with customElements.define method that we have discussed above.
on line no 6. we are accessing the template tag with unique id and then content of template tag with template.content .
on line no 9. creating the shadow root with attachShadow and append template content to it.

The key point to note here is that we appended a clone of the template content to the shadow root, that’s been created using the Node.cloneNode() method. cloneNode() returns a duplicate of the node on which this method was called. cloneNode contains optional boolean parameter if true then whole subtree including child and text will be cloned , in case of false only node will be cloned not all subtree.

You have to add my-custom-element in body tag to render it’s content.

If you go to developer console then there is shadow root attached with my-custom-element .

shadow root

slot with template tag:-

In the above example the content of template tag is static means it will render same content for every <my-custom-element> tag. we can make it dynamic like each instance of <my-custom-element> will render their own content using html <slot> element. slot tag has more limited support than <template>, available since Chrome 53, Opera 40, Safari 10, Firefox 59, and Edge 79.

Slots are identified by their name attribute, and allow you to define placeholders in your template that can be filled with any markup fragment you want when the element is used in the markup.

If the slot’s content isn’t defined when the element is included in the markup, or if the browser doesn’t support slots, then the fallback content will be rendered.

you can create the slot like:-

<summary>
<span>
<code class="name">&lt;<slot name="element-name">NEED NAME</slot>&gt;</code>
<span class="desc"><slot name="description">NEED DESCRIPTION</slot></span>
</span>
</summary>

here we are using <slot> with name property and with fallback content in it. And we can replace the fallback content with dynamic content using slot name like:-

<my-custom-element>
<span slot="element-name">template</span>
<span slot="description">A mechanism for holding client-
side content that is not to be rendered when a page is
loaded but may subsequently be instantiated during
runtime using JavaScript.</span>
</my-custom-element>

here in the the span tag we are giving slot name like element-name and content will be replaced by fallback content. so, each of the <my-custom-element> have their own content in the shadow root template.

working example:-

here we can see that in the second <my-custom-element> I have not declared content for slot name attributes and fallback content has been rendered over there.

Benefits of using shadow Dom:-

It offers solutions to common problems in web development you’ve probably experienced:

  • Isolated DOM: A component’s DOM is self-contained (e.g. document.querySelector() won’t return nodes in the component’s shadow DOM). This also simplifies the CSS selectors across your web app since DOM components are isolated, and it gives you the ability to use more generic id/class names without worrying about naming conflicts.
  • Scoped CSS: CSS defined inside shadow DOM is scoped to it. Style rules don’t leak out and page styles don’t interfere with it.
  • Composition: Design a declarative, markup-based API for your component.
  • Declarative Shadow DOM: bringing Shadow DOM to the server (this is experimental feature) and only supported by Chrome 90 and Edge 91.

Browser Support:-

Shadow DOM Browser Support
Declarative Shadow Dom Browser Support

--

--

Vinay Mittal
Frontend Weekly

Software Developer@Deutsche Telekom Digital Labs