Skip to content

Attaching a Shadow Root

To create a shadow tree, call this.attachShadow() on the host element and pass a mode option. The method returns the new shadow root and also stores it as this.shadowRoot.

class ScopedCard extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
}
}

You can call attachShadow in the constructor or in connectedCallback. The constructor is preferred when you want the shadow root available immediately; connectedCallback is fine when you also need access to attributes set in HTML.

After calling attachShadow, the shadow root is available two ways:

// returned directly by the call
var root = this.attachShadow({ mode: 'open' });
// also stored as a property on the element
var root = this.shadowRoot;

Both references point to the same ShadowRoot object. The ShadowRoot behaves like a DocumentFragment — you can call querySelector, getElementById, and set innerHTML on it.

The simplest way to populate the shadow tree is to set this.shadowRoot.innerHTML. Place your <style> tag first, followed by your markup:

this.shadowRoot.innerHTML =
'<style>' +
' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' +
' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' +
'</style>' +
'<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>';

The styles only apply inside the shadow tree. Any .card element that exists in the light DOM is unaffected.

class ScopedCard extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML =
'<style>' +
' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' +
' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' +
' p { color: #3730a3; margin: 0; }' +
'</style>' +
'<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>';
}
}
customElements.define('scoped-card', ScopedCard);
OptionBenefitCost
Call attachShadow in the constructorThe shadow root is ready immediately as soon as the instance is createdYou still can’t read attributes from HTML at this point
Call attachShadow in connectedCallbackYou can read attributes declared in HTML from the very first time the element enters the DOMYou have to check whether a shadow root already exists in case the element is reconnected
  • Calling attachShadow more than once — if the element is moved in the DOM (disconnectedCallback followed by connectedCallback again) and your code calls attachShadow again every time, you get an error, because a shadow root can only be attached once per element. Check this.shadowRoot before calling it again.
  • Setting innerHTML before calling attachShadow — this.shadowRoot stays null until attachShadow has been called; always call it first.
  • Concatenating HTML strings by hand from user input — building innerHTML like the example above is convenient for a demo, but if the data comes from outside, use textContent or sanitize it first, or you risk XSS.

💡 Real-world example

Salesforce Lightning (LWC) — calls attachShadow as part of every component’s lifecycle, to encapsulate enterprise-scale UI built by many teams at once. (salesforce/lwc)

What does `this.attachShadow({ mode: "open" })` return?
After calling `this.attachShadow({ mode: "open" })`, how do you access the shadow root later?
Where must you place the `<style>` tag to scope styles to the shadow tree?