Declarative Shadow DOM
What is Declarative Shadow DOM
Section titled “What is 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.
The shadowrootmode attribute
Section titled “The shadowrootmode attribute”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.
Hydration story
Section titled “Hydration story”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);Browser support note
Section titled “Browser support note”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 browsersdocument.querySelectorAll('template[shadowrootmode]').forEach(function(t) { t.parentElement.attachShadow({ mode: t.getAttribute('shadowrootmode') }) .appendChild(t.content.cloneNode(true)); t.remove();});Runnable demo
Section titled “Runnable demo”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.