Shadow DOM Overview
Shadow DOM lets an element own a separate, encapsulated DOM subtree — its "shadow tree" — whose markup and styles are isolated from the rest of the page. Outside CSS does not leak in, and the shadow tree's own CSS does not leak out. This isolation is what makes truly reusable, drop-in-anywhere Web Components possible.
Attaching a Shadow Root
Any element can host a shadow tree by calling
element.attachShadow({ mode }). This returns a
ShadowRoot — a special document fragment you can populate like
any other DOM node.
basic-shadow-dom.html
<div id="host"></div>
<script>
const host = document.getElementById('host')
const shadow = host.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
p { color: crimson; font-weight: bold; }
</style>
<p>Styled only inside this shadow tree.</p>
`
</script>
<!-- A page-level style like "p { color: blue; }" will NOT affect
the paragraph above, and this shadow tree's "p { color: crimson; }"
will NOT leak out to affect any other paragraph on the page. -->Open vs Closed Mode
The mode option controls whether outside JavaScript can reach
into the shadow tree via element.shadowRoot.
Mode | element.shadowRoot from outside | Typical use |
|---|---|---|
| Returns the | The vast majority of components; easier to debug and test |
| Returns | Rare; used when you need to strictly prevent external code from poking at internals |
open.Style Encapsulation — the Main Benefit
Before Shadow DOM, every stylesheet on a page was global: a rule like
button { color: red; } anywhere could affect every
button on the page, including ones inside a component you did not write and
did not expect to change. Shadow DOM fixes this in both directions.
CSS written inside a shadow tree only ever styles elements inside that same shadow tree.
CSS written in the main document does not style elements inside a shadow tree.
Class name collisions between a component and the host page become impossible — no more
.cardvs.cardconflicts.Components can ship with sensible default styles without worrying they will "leak" and affect unrelated markup.
encapsulation-demo.html
<style>
/* Page-level style */
p { color: blue; }
</style>
<p>I am blue, from the page stylesheet.</p>
<div id="host"></div>
<script>
const shadow = document.getElementById('host').attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>p { color: green; }</style>
<p>I am green — the page's blue rule cannot reach in here.</p>
`
</script>Page paragraph: blue text
Shadow paragraph: green text (unaffected by the page's "p { color: blue }")How Shadow DOM Fits with Custom Elements & Templates
Shadow DOM is rarely used on a plain <div> in real
projects — it is almost always paired with a Custom Element, whose class
attaches the shadow root in connectedCallback, and a
<template> that supplies the reusable markup to clone
into it.
full-picture.js
const template = document.createElement('template')
template.innerHTML = `
<style>
.card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; }
</style>
<div class="card">
<slot name="title"></slot>
<slot></slot>
</div>
`
class InfoCard extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' })
shadow.appendChild(template.content.cloneNode(true))
}
}
customElements.define('info-card', InfoCard)<template> supplies inert markup, Custom Elements supply the lifecycle and tag name, Shadow DOM supplies encapsulation, and <slot> supplies content projection. See the dedicated pages on each for the full picture.Shadow DOM vs Regular (Light) DOM
Aspect | Light DOM | Shadow DOM |
|---|---|---|
Visible to | Yes | No — must query via the element's own |
Affected by page-level CSS? | Yes | No, by default |
Its own CSS affects the page? | Yes | No, by default |
Can accept projected content? | N/A | Yes, via |
<img>, <input>) cannot have a shadow root attached, either because the browser already manages internal, hidden rendering for them or for security reasons. Custom elements and common containers like <div> and <span> work fine.Use Shadow DOM whenever you need genuine CSS/DOM isolation for a reusable component.
Default to
mode: "open"unless you have a specific reason to restrict access.Pair it with
<template>for markup and<slot>for content projection to build complete Web Components.