Shadow DOM

Overview

The shadow DOM, together with slot elements, custom elements and templates compose the Web Components specification. Web components allow for easier creation and reuse of encapsulated components and possibly increased performance.

Encapsulation is achieved by using the shadow DOM, which is a a set of JS APIs which allow the creation of encapsulated DOM subtrees which can have their own style that has no effect outside the shadow DOM, while the elements inside do not get matched with rules outside the shadow DOM or in different shadow trees. This, along with shadow DOM specific differences, like querySelector ignoring shadow subtrees, allows for creation of encapsulated components that can’t clash due to same ids or class names etc.

Performance gains can be seen in cases where complex selectors are used or styles that will change only a small portion of the elements are often altered. The performance benefits greatly depend on the page and the way the shadow DOM is used, for example a page with only inline styles will not benefit at all, while a page that heavily uses complex selectors will likely improve its performance significantly.

The slot element allows for creating encapsulated components, that can still be modified by the end user, it is a placeholder inside a shadow tree, which can be filled by the user of the component. The rendered subtree will look like the currently slotted elements have replaced the slot, while the DOM tree itself can remain unchanged.

Example

<style>
    /* This will not match with the shadow element */
    #idInShadow {
        background-color: black;
    }
</style>
<template id="custom-component-template">
    <style>
        /* This will not have an effect on elements outside the shadow DOM */
        div {
            background-color: green;
            width: 100px;
            height: 100px;
            border-radius: 50%;
        }
        /* The slotted element will have its original styles + the styles from shadow dom ::slotted  */
        ::slotted(*) {
            background-color: red;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            transform: translate(25px,25px);
        }
    </style>
    <div id="idInShadow">
        <slot>default slot value</slot>
    </div>
</template>

<script>
    const template = document.getElementById('custom-component-template');
    const templadeNode = template.content.cloneNode(true);
    customElements.define('custom-component', class extends HTMLElement {
        constructor() {
            super();
            this.attachShadow({mode: 'closed'}).appendChild(templadeNode.cloneNode(true));
        }
    });
</script>

<!-- can use the component and add any element which will rendered as if its in the place of <slot> in the shadow Tree-->
<custom-component>
    <div></div>
</custom-component>

<custom-component>
    <div style="background-color: blue;"></div>
</custom-component>

Supported subset

  • ShadowRoot
    • The options mode, delegatesFocus, slotAssignment, clonable
    • host
    • innerHTML
    • activeElement
    • styleSheets
    • elementFromPoint
    • elementsFromPoint
  • Slot element
    • named assignment through name and slot attributes
    • manual assignment through assign()
    • unnamed assignment
    • assignedNodes
    • assignedElements
  • shadowRoot
  • attachShadow - all options except serializable
  • assignedSlot
  • slotchange event
  • :host() selector
  • ::slotted() selector
  • Also added support for all shorthand property with value of initial which allows for easy complete encapsulation from inherited styles by using:
    :host { all: initial !important; }

Known issues

  • @keyframe rules in shadow DOM get out of shadow encapsulation.
  • Slot element’s default display value is block instead of contents as it is not supported.
  • Using data-bind-for and data-bind-if on subtrees that contain a shadow tree require it to be created with clonable: true option to function correctly. For example, this will be needed when using these data-bind attributes on custom elements that use attachShadow in their constructors.