Element: attachShadow() method

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.

* Some parts of this feature may have varying levels of support.

The attachShadow() method of the Element interface attaches a shadow DOM tree to the specified element and returns a reference to its ShadowRoot.

Syntax

js
attachShadow(options)

Parameters

options

An object which contains the following fields:

mode

A string specifying the encapsulation mode for the shadow DOM tree. This can be one of:

open

Elements inside the shadow root are accessible from JavaScript via the element's shadowRoot property.

closed

Elements inside the shadow root cannot be accessed from JavaScript via the shadowRoot property, which is set to null.

clonable Optional

A boolean that specifies whether the shadow root is clonable: when set to true, the shadow host cloned with Node.cloneNode() or Document.importNode() will include shadow root in the copy. Its default value is false.

customElementRegistry Optional

A CustomElementRegistry that will be used as the scoped custom element registry of the attached shadow root. If null or undefined, the shadow root will use the global registry referenced by Window.customElements.

delegatesFocus Optional

A boolean that, when set to true, specifies behavior that mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling. Its default value is false.

referenceTarget Optional

A string value that indicates the effective target of any element reference made against the shadow host from outside the host element. The value should be the ID of an element inside the shadow DOM. If set, target references to the host element from outside the shadow DOM will cause the referenced target element to become the effective target of the reference to the host element.

serializable Optional

A boolean that, when set to true, indicates that the shadow root is serializable. If set, the shadow root may be serialized by calling the Element.getHTML() or ShadowRoot.getHTML() methods with the options.serializableShadowRoots parameter set true. Its default value is false.

slotAssignment Optional

A string specifying the slot assignment mode for the shadow DOM tree. This can be one of:

named

Elements are automatically assigned to <slot> elements within this shadow root. Any top-level children of the host with a slot attribute which matches the name attribute of a <slot> within this shadow root will be assigned to that slot. Any top-level children of the host with no slot attribute will be assigned to the first <slot> with no name attribute (the "default slot"), if one is present. This is the default value.

manual

Elements are manually assigned to particular slot elements using HTMLSlotElement.assign(). No automatic assignment takes place.

Return value

Returns a ShadowRoot object.

Exceptions

NotSupportedError DOMException

This error may be thrown when you try to attach a shadow root to an element:

  • outside the HTML namespace or that can't have a shadow attached to it.
  • where the element definition static property disabledFeatures has been given a value of "shadow".
  • that already has a shadow root that was not created declaratively.
  • that has a declarative shadow root but the specified mode does not match the existing mode.
  • while passing a customElementRegistry value that isn't null or a locally scoped registry (that you created using new CustomElementRegistry()). The error would be thrown if you passed the global registry.

Description

The Element.attachShadow() method attaches a shadow DOM tree to the specified element and returns a reference to its ShadowRoot.

This is the programmatic mechanism to create a ShadowRoot, which is the root node of a Shadow DOM attached to a host element (it is also possible to create a ShadowRoot declaratively using the shadowrootmode attribute of the <template> element). It is used for creating custom elements.

Elements you can attach a shadow to

Note that you can't attach a shadow root to every type of element. There are some that can't have a shadow DOM for security reasons (for example <a>).

The following is a list of elements you can attach a shadow root to:

Calling this method on an element that is already a shadow host

The method may be called on an element that already has a declarative shadow root, provided the specified mode mode matches the existing mode. In this case the ShadowRoot that was already present will be cleared and returned. This allows for cases where, for example, server-side rendering has already declaratively created a shadow root, and then client-side code attempts to attach the root again.

Otherwise calling attachShadow() on an element that already has a shadow root will throw an exception.

Open and closed shadow roots

A shadow root can be attached with an encapsulation mode, which is specified as either open or closed.

If the {mode: "open"} argument is passed, the host element's shadowRoot property can subsequently be used to get the attached shadow root. This can be used to access elements in the Shadow DOM:

js
element.attachShadow({ mode: "open" });
element.shadowRoot; // Returns a ShadowRoot obj

If {mode: "closed"} is passed then the Element's shadowRoot property is set to null. Note that JavaScript can still access a closed shadow root by storing the value returned by the function.

js
element.attachShadow({ mode: "closed" });
element.shadowRoot; // Returns null

Examples

Word count custom element

The following example is taken from our word-count-web-component demo (see it live also). You can see that we use attachShadow() in the middle of the code to create a shadow root, which we then attach our custom element's contents to.

js
// Create a class for the element
class WordCount extends HTMLParagraphElement {
  constructor() {
    // Always call super first in constructor
    super();

    // count words in element's parent element
    const wcParent = this.parentNode;

    function countWords(node) {
      const text = node.innerText || node.textContent;
      return text
        .trim()
        .split(/\s+/g)
        .filter((a) => a.trim().length > 0).length;
    }

    const count = `Words: ${countWords(wcParent)}`;

    // Create a shadow root
    const shadow = this.attachShadow({ mode: "open" });

    // Create text node and add word count to it
    const text = document.createElement("span");
    text.textContent = count;

    // Append it to the shadow root
    shadow.appendChild(text);

    // Update count when element content changes
    this.parentNode.addEventListener("input", () => {
      text.textContent = `Words: ${countWords(wcParent)}`;
    });
  }
}

// Define the new element
customElements.define("word-count", WordCount, { extends: "p" });

Disabling shadow DOM

If the element has a static property named disabledFeatures, which is an array containing the string "shadow", then the attachShadow() call will throw an exception.

For example:

js
class MyCustomElement extends HTMLElement {
  // Disable shadow DOM for this element.
  static disabledFeatures = ["shadow"];

  constructor() {
    super();
  }

  connectedCallback() {
    // Create a shadow root.
    // This will throw an exception.
    const shadow = this.attachShadow({ mode: "open" });
  }
}

// Define the new element
customElements.define("my-custom-element", MyCustomElement);

Named slot assignment

This example demonstrates named slot assignment.

Creating the web component

This code creates a web component that has three named slots for an article's title, metadata, and body section.

The ShadowRoot is attached in the custom element's constructor. We don't need to explicitly set the option slotAssignment: "named" because it is the default.

js
class MyArticle extends HTMLElement {
  constructor() {
    super();
    // Attach the shadow root
    this.attachShadow({ mode: "open" /*, slotAssignment: "named"*/ });
  }

  connectedCallback() {
    this.render();
  }

  render() {
    // Define the internal structure and styles
    this.shadowRoot.innerHTML = `
      <style>
        .header {
          background-color: plum;
        }
        .meta {
          background-color: green;
        }
        .body {
          background-color: lightblue;
        }
      </style>

      <h2 class="header">
        <slot name="title"></slot>
      </h2>

      <div class="meta">
        <slot name="meta"></slot>
      </div>

      <div class="body">
        <slot></slot>
      </div>
    `;
  }
}

// Register the component
customElements.define("my-article", MyArticle);

Using the web component

The HTML below uses the <my-article> web component we just created. The nested elements are rendered in the component's slots based on name matching. The unnamed elements are rendered in the component's unnamed slot (the body).

html
<my-article>
  <span slot="title">Text for the title slot</span>
  <span slot="meta">Text for the meta slot</span>

  <p>
    Text 1 with no slot attribute. Goes into default (unnamed) slot inside the
    "body" div.
  </p>
  <p>
    Text 2 with no slot attribute. Also goes into default (unnamed) slot inside
    the "body" div.
  </p>
</my-article>

Results

The example below should show the content of the slots displayed in the appropriate sections.

Unnamed slot assignment

This example demonstrates manual slot assignment. With this approach, each element must be manually assigned to a particular slot using HTMLSlotElement.assign(). There is no default assignment, so any slot that is not assigned will be empty.

HTML

First we have a hidden support warning, displayed via JavaScript if the browser doesn't support slotAssignment: "manual".

html
<p id="support-warning" hidden>
  ⛔ Your browser doesn't support manual slot assignment (named assignment is
  used).
</p>

Next, we define our <my-article> custom element with child elements for the title, metadata, and body content. Each child is identified by id; unlike named slot assignment, no slot attribute is needed.

html
<my-article>
  <span id="text_title">Text for the title slot</span>
  <span id="text_meta">Text for the meta slot</span>
  <p id="text_body_1">Text 1 for body slot.</p>
  <p id="text_body_2">Text 2 for body slot.</p>
</my-article>

JavaScript

The custom element attaches a shadow root with slotAssignment: "manual". The shadow DOM contains unnamed slots identified by id. The assignSlots() method manually assigns the light DOM elements to the slots. Note that multiple nodes can be assigned to a single slot — the order they are specified controls the render order.

js
class MyArticle extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open", slotAssignment: "manual" });
  }

  connectedCallback() {
    this.render();
    this.assignSlots();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        .header {
          background-color: plum;
        }
        .meta {
          background-color: green;
        }
        .body {
          background-color: lightblue;
        }
      </style>

      <h2 class="header">
        <slot id="titleSlot"></slot>
      </h2>

      <div class="meta">
        <slot id="metaSlot"></slot>
      </div>

      <div class="body">
        <slot id="bodySlot"></slot>
      </div>
    `;
  }

  assignSlots() {
    // 1. Target your slots
    const titleSlot = this.shadowRoot.querySelector("#titleSlot");
    const metaSlot = this.shadowRoot.querySelector("#metaSlot");
    const bodySlot = this.shadowRoot.querySelector("#bodySlot");

    // 2. Target your light DOM elements
    const titleText = this.querySelector("#text_title");
    const metaText = this.querySelector("#text_meta");
    const body1Text = this.querySelector("#text_body_1");
    const body2Text = this.querySelector("#text_body_2");

    // 3. Manually assign them
    titleSlot.assign(titleText);
    metaSlot.assign(metaText);
    bodySlot.assign(body2Text, body1Text);
  }
}

customElements.define("my-article", MyArticle);

This code tests if the ShadowRoot.slotAssignment property is defined, and displays the warning if it is not.

js
const isSlotAssignmentSupported = Object.hasOwn(
  ShadowRoot.prototype,
  "slotAssignment",
);

document
  .querySelector("p[hidden]")
  .toggleAttribute("hidden", isSlotAssignmentSupported);

Results

The example below should show the content of the slots displayed in the appropriate sections.

Note: If manual slot assignment is not supported, a warning is displayed and the browser will use named assignment. However, because none of the light DOM elements have a slot attribute, they will all be inserted into the first unnamed slot (the title slot).

Specifications

Specification
DOM
# dom-element-attachshadow

Browser compatibility

See also