JavaScript Element attachShadow() Method

Beginner
⏱️ 12 min read
📚 Updated: Jul 2026
🎯 5 Examples
🚀 5 Try-it labs
Baseline
Instance method

What You’ll Learn

Element.attachShadow() is an instance method that attaches a Shadow DOM tree to a host element and returns a ShadowRoot. Learn open vs closed mode, custom elements, slots, and five try-it labs.

01

Kind

Instance method

02

Returns

ShadowRoot

03

Mode

open / closed

04

Host

Element + tree

05

Used for

Custom elements

06

Status

Baseline widely

Introduction

Normal DOM children are part of the page’s main tree. A shadow tree is a separate DOM attached to a host element. Styles and markup inside the shadow root stay encapsulated—ideal for reusable components.

attachShadow() is the JavaScript way to create that root (you can also create one declaratively with the shadowrootmode attribute). It is the foundation of many custom elements.

💡
Beginner tip

Start with { mode: "open" }. Then you can read the tree later with element.shadowRoot.

This page is part of JavaScript Element. Related topics include shadowRoot and append().

Understanding the attachShadow() Method

Calling el.attachShadow(options) makes el a shadow host and returns the new ShadowRoot. You then append nodes (or set innerHTML) on that root.

  • It is an instance method on Element.
  • mode controls whether el.shadowRoot stays readable.
  • Not every tag can host a shadow root (security / platform limits).
  • Calling again on an existing programmatic shadow root usually throws.
  • Optional flags: delegatesFocus, slotAssignment, clonable, serializable, and more.

📝 Syntax

General form of Element.attachShadow (MDN):

JavaScript
attachShadow(options)

Parameters

options — an object that includes:

  • mode (required) — "open" (readable via element.shadowRoot) or "closed" (shadowRoot is null).
  • clonable (optional) — include the shadow root when cloning the host (default false).
  • delegatesFocus (optional) — focus the first focusable part when clicking non-focusable shadow content (default false).
  • slotAssignment (optional) — "named" (default, automatic slots) or "manual" (HTMLSlotElement.assign()).
  • serializable (optional) — allow serialization via getHTML() options (default false).
  • customElementRegistry (optional) — scoped registry for the shadow root.
  • referenceTarget (optional) — ID of an inner element that becomes the effective target of external references to the host.

Return value

Returns a ShadowRoot object.

Exceptions

MDN: may throw when attaching to an unsupported element, when shadow is disabled via custom element disabledFeatures, when a conflicting shadow root already exists, when declarative mode mismatches, or when an invalid customElementRegistry is passed.

Common patterns

JavaScript
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = "

Inside the shadow tree

"; // Later (open mode only): host.shadowRoot.querySelector("p"); const closed = host2.attachShadow({ mode: "closed" }); host2.shadowRoot; // null — keep `closed` yourself

⚡ Quick Reference

GoalCode
Create open shadowel.attachShadow({ mode: "open" })
Create closed shadowel.attachShadow({ mode: "closed" })
Read open root laterel.shadowRoot
Add contentshadow.append(...) / innerHTML
Named slots (default)slotAssignment: "named"
MDN statusBaseline Widely available (since January 2020)

🔍 At a Glance

Four facts to remember about Element.attachShadow().

Returns
ShadowRoot

Root of the shadow tree

Baseline
widely

Since January 2020

Open mode
shadowRoot

Readable on the host

Closed mode
null

Save the return value

📋 Open vs closed shadow roots

{ mode: "open" }{ mode: "closed" }
element.shadowRootReturns the ShadowRootAlways null
Access from your codeVia host or return valueOnly via saved return value
EncapsulationStrong for CSS / structureHarder for outsiders to poke in
Beginner defaultYes — easier debuggingWhen you intentionally hide the tree

Examples Gallery

Examples follow MDN Element.attachShadow() patterns. Use View Output or Try It Yourself for each case.

📚 Getting Started

Attach open and closed shadow roots on a simple host.

Example 1 — Open Shadow Root

Attach an open tree and add content inside it.

JavaScript
const host = document.getElementById("host");
const shadow = host.attachShadow({ mode: "open" });

const p = document.createElement("p");
p.textContent = "Hello from the shadow DOM";
shadow.appendChild(p);

console.log(host.shadowRoot === shadow); // true
Try It Yourself

How It Works

The paragraph lives in the shadow tree. Page CSS outside the host usually does not style it the same way as light DOM children.

Example 2 — Closed Shadow Root

With closed mode, shadowRoot on the host is null.

JavaScript
const host = document.getElementById("host");
const shadow = host.attachShadow({ mode: "closed" });

shadow.innerHTML = "Secret internals";

console.log(host.shadowRoot); // null
console.log(shadow.querySelector("span").textContent);
// "Secret internals" — only via the saved reference
Try It Yourself

How It Works

Closed mode hides the tree from element.shadowRoot. Your component code must keep the return value if it still needs to update the shadow DOM.

📈 Practical Patterns

Custom elements, named slots, and reading an open root later.

Example 3 — Custom Element (MDN-style)

Attach a shadow root in a custom element constructor and render inside it.

JavaScript
class HelloCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `
      
      Hello, custom element!
    `;
  }
}

customElements.define("hello-card", HelloCard);
Try It Yourself

How It Works

Each <hello-card> instance gets its own shadow tree. Styles inside the shadow root stay scoped to that component.

Example 4 — Named Slot Assignment (MDN)

Default slotAssignment: "named" projects light DOM into <slot>s.

JavaScript
class MyArticle extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" /* named is default */ });
  }
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      

`; } } customElements.define("my-article", MyArticle); // Light DOM: // <my-article> // <span slot="title">Title text</span> // <p>Body goes to default slot</p> // </my-article>
Try It Yourself

How It Works

Children with slot="title" fill the named slot. Children without a slot attribute go into the default (unnamed) slot.

Example 5 — Read Content via shadowRoot

After attaching an open root, query it from the host.

JavaScript
const host = document.getElementById("host");
host.attachShadow({ mode: "open" }).innerHTML =
  '';

const btn = host.shadowRoot.getElementById("go");
btn.addEventListener("click", () => {
  console.log("Clicked inside shadow");
});

console.log(host.shadowRoot.mode); // "open"
Try It Yourself

How It Works

host.shadowRoot is the same open root returned by attachShadow(). Use normal DOM APIs on it.

🚀 Common Use Cases

  • Building custom elements with encapsulated markup and CSS.
  • Hiding internal structure from global page styles.
  • Projecting light DOM into slots for flexible component APIs.
  • Pairing with declarative shadow DOM for SSR / progressive enhancement.
  • Teaching open vs closed encapsulation for beginners.
  • Focus-friendly widgets with delegatesFocus: true.

🧠 How attachShadow() Builds a Tree

1

Choose a host element

Call the method on an element that is allowed to host a shadow root.

Host
2

Pass options (mode required)

Set open/closed and optional slot, focus, or clone behavior.

Options
3

Receive a ShadowRoot

Append nodes or set innerHTML on the returned root.

Tree
4

Access later if open

Use host.shadowRoot, or keep the return value for closed mode.

📝 Notes

  • Not Deprecated, Experimental, or Non-standard on MDN (Baseline Widely available since January 2020).
  • Some options (slotAssignment: "manual", serializable, scoped registries) may have narrower support—check compatibility.
  • Custom elements can disable shadow via static disabledFeatures = ["shadow"].
  • Declarative shadow DOM uses shadowrootmode on a <template>.
  • Related: shadowRoot, slot, append(), JavaScript hub.

Browser Support

Element.attachShadow() is Baseline Widely available (MDN: across browsers since January 2020). Logos use the shared browser-image-sprite.png sprite from this project. Newer options may vary.

Baseline Widely available

Element.attachShadow()

Safe for production Shadow DOM and custom elements. Prefer open mode while learning.

Baseline Widely available
Google Chrome Supported · Desktop & Mobile
Yes
Microsoft Edge Supported · Chromium
Yes
Mozilla Firefox Supported · Desktop & Mobile
Yes
Apple Safari Supported · macOS & iOS
Yes
Opera Supported · Modern versions
Yes
Internet Explorer Not supported
No
attachShadow() Excellent

Bottom line: Use attachShadow() to create encapsulated component trees. Start with mode: "open", then explore slots and closed mode as needed.

Conclusion

Element.attachShadow() creates a ShadowRoot on a host element for encapsulated markup and styles. Use open mode for easy access via shadowRoot, and explore slots when building flexible custom elements.

Continue with shadowRoot, slot, append(), or the JavaScript hub.

💡 Best Practices

✅ Do

  • Start with { mode: "open" } while learning
  • Attach once per host in the custom element constructor
  • Keep closed-mode roots in a private field if you need them
  • Use slots for light DOM composition
  • Scope component CSS inside the shadow tree

❌ Don’t

  • Call attachShadow() twice on the same programmatic host
  • Assume every HTML tag can host a shadow root
  • Expect element.shadowRoot to work in closed mode
  • Leak global styles that break component encapsulation unintentionally
  • Forget newer options may need capability checks

Key Takeaways

Knowledge Unlocked

Five things to remember about Element.attachShadow()

Create an encapsulated ShadowRoot on a host element.

5
Core concepts
🔒 02

Mode

open / closed

Access
🧩 03

Used in

custom elements

Components
04

Baseline

widely available

Status
05

Slots

named / manual

Compose

❓ Frequently Asked Questions

It attaches a shadow DOM tree to an element (the shadow host) and returns a ShadowRoot. You then append markup and styles inside that root for encapsulation.
No. MDN marks Element.attachShadow() as Baseline Widely available (across browsers since January 2020). It is not Deprecated, Experimental, or Non-standard. Some newer options may have narrower support.
With mode: "open", element.shadowRoot returns the ShadowRoot. With mode: "closed", element.shadowRoot is null — keep the return value from attachShadow() if you still need access from your own code.
No. Only certain elements can (for example div, span, section, and custom elements). Some elements cannot for security reasons. Attaching to an unsupported element throws.
Calling attachShadow() again usually throws. A special case is a matching declarative shadow root: the existing ShadowRoot can be cleared and returned when modes match.
For open mode use host.shadowRoot. For closed mode use the ShadowRoot reference you saved from attachShadow().
Did you know?

You can also create a shadow root declaratively with a <template shadowrootmode="open"> (or closed). Client code can still call attachShadow() when modes match to take over a server-rendered declarative root.

More Element Topics

Browse Shadow DOM properties and methods to keep building components.

shadowRoot →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

8 people found this page helpful