The Rise of Shadow DOM

Photo by Zack Woolwine on Unsplash

CSS rules are applied globally within a document. Shadow DOM enables to create self-contained HTML, CSS, and JavaScript. (i.e. Web Components) This is the rise of shadow DOM.

Shadow DOM is a separate DOM tree of elements which is not part of main document DOM but when it renders, it behaves as part of the main document DOM.
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 — attachShadow

Result on browser

The ‘Inside Shadow DOM’ and ‘Outer Element’ are visible. However the ‘Shadow Root’ is hidden. The global css class gets applied to the DOM outside Shadow DOM. This reflects the idea of scoped css.

Types of Shadow DOM Modes — Open vs Closed

var shadowRoot = host.attachShadow({mode: 'open'});
console.log(host.shadowRoot);
Open Shadow DOM
var shadowRoot = host.attachShadow({mode: 'closed'});
console.log(host.shadowRoot); //null

Open: The shadow DOM/ internal DOM of the component is accessible from outside JavaScript.

Closed: The shadow DOM/ internal DOM of the component is not accessible outside JavaScript. The <video> tag is an example of closed-mode shadow root.

Creating shadow DOM — Using Template Approach

As the content of the Shadow DOM increases, making use of appendChild/innerHTML is not feasible. So template tags are used to better create Shadow Elements.

Template HTML tag are not displayed.The content can be visible and rendered later by using JavaScript.

<div id="shadowHost">Shadow Root</div>
<template id="demo">
<style>*{ color: blue; }</style>
<div><span class="name">Inside Shadow DOM</span></div>
</template>
<div>
<div class="x">Outer Element</div>

JS

var host = document.querySelector('#shadowHost');
var shadowRoot = host.createShadowRoot();
shadowRoot.appendChild(document.querySelector('#demo').content);

Creating Web Components Using Shadow DOM

<my-web-component></my-web-component>
<div class="x">Outer Element</div>

CSS

.x{
color:red;
}

JS

class MyWebComponent extends HTMLElement {

constructor() {
super();
this.attachShadow({
mode: "open"
});
}

connectedCallback() {
    var div = document.createElement('div');
div.textContent = "Inside Shadow DOM";
div.className = "x";
    this.shadowRoot.appendChild(div);
  }
}
window.customElements.define("my-web-component", MyWebComponent);

Web Components Using Template tag

HTML

<div id="shadowHost">Shadow Root</div>
<template id="demo">
<style>*{ color: blue; }</style>
<div><span class="name">Inside Shadow DOM</span></div>
</template>
<div>
<div class="x">Outer Element</div>

JS

class MyWebComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open"
});
}
connectedCallback() {
   this.shadowRoot.appendChild(
document.querySelector('#demo').content);
  }
}
window.customElements.define("my-web-component", MyWebComponent);

With Open Shadow Mode the inner content of the shadow DOM is accessible from outside

const $myWebComponent = document.querySelector("my-web-component");
$myWebComponent.shadowRoot.querySelector("div").innerText = "Modify inner content";

The difference with Closed Shadow Mode is how inner content of the shadow DOM is accessible from outside.

const $myWebComponent = document.querySelector("my-web-component");
$myWebComponent.shadowRoot; // null
$myWebComponent._root.querySelector("div").innerText = "Changed";

Slots in Web Components:

Slots provide flexibility to web components to add custom content within the component.

Slots are defined using the slot tag and name attribute and the default content . This tag is pace within the components template tag.

For making use of slots , include an HTML structure within my-web-component with a slot attribute whose value is equal to the name (innerText) of the slot we wish to fill in.

<my-web-component>
<span slot="innerText">Input Inner Text</span>
</my-web-component>
<template id="demo">
<style>*{ color: blue; }</style>
<div>
<slot name="innerText">Default Inside Shadow DOM</slot>
</div>
</template>
<div class="x">Outer Element</div>

If the slot is not used , then the default content is printed as it is.

<my-web-component>
</my-web-component>
<template id="demo">
<style>*{ color: blue; }</style>
<div>
<slot name="innerText">Default Inside Shadow DOM</slot>
</div>
</template>
<div class="x">Outer Element</div>