Skip to content

Declarative Shadow DOM

Declarative Shadow DOM (DSD) lets you attach a shadow root to an element using only HTML, without any JavaScript. This is ideal for server-side rendering (SSR), where the shadow tree can be included in the initial HTML response and the browser attaches it before any JavaScript runs.

Placing a <template shadowrootmode="open"> (or "closed") as a direct child of an element causes the browser to automatically attach a shadow root to the parent and move the template’s content into it.

<my-card>
<template shadowrootmode="open">
<style>
p { color: #0070f3; font-weight: bold; }
</style>
<p>This content lives in the shadow root.</p>
<slot></slot>
</template>
Light DOM content here (goes into the slot)
</my-card>

The <template> tag is consumed by the browser and replaced with an attached shadow root. No JavaScript needed — the element has a shadow root immediately on parse.

When JavaScript does load, customElements.define('my-card', MyCard) upgrades the element. If the class calls attachShadow in its constructor, the browser skips creating a second shadow root (it already exists) and reuses the existing one. This means the server-rendered content is preserved — a critical property for SSR hydration.

class MyCard extends HTMLElement {
connectedCallback() {
// Shadow root already attached declaratively — shadowRoot is available
const shadow = this.shadowRoot; // not null
if (shadow) {
// hydrate: add event listeners, update state, etc.
}
}
}
customElements.define('my-card', MyCard);

Declarative Shadow DOM is supported in Chrome 90+, Edge 90+, Safari 16.4+, and Firefox 123+. In older browsers, the <template shadowrootmode> attribute is unknown and the template remains inert. Include a polyfill for older browsers:

// Polyfill for older browsers
document.querySelectorAll('template[shadowrootmode]').forEach(function(t) {
t.parentElement.attachShadow({ mode: t.getAttribute('shadowrootmode') })
.appendChild(t.content.cloneNode(true));
t.remove();
});

The LivePreview below shows a <dsd-card> with a <template shadowrootmode="open"> already in the HTML. The slot receives the light-DOM text. No JavaScript is required for the shadow to attach.

Which HTML attribute on a template element triggers Declarative Shadow DOM?
When does the browser attach the shadow root in Declarative Shadow DOM?
What happens to this.shadowRoot in a custom element class when a declarative shadow root already exists?