HTMLShadow DOM Overview

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

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

open

Returns the ShadowRoot object — fully accessible

The vast majority of components; easier to debug and test

closed

Returns null — inaccessible from outside script

Rare; used when you need to strictly prevent external code from poking at internals

Closed mode is not a security boundary
Closed mode stops casual DOM access, but it does not provide real security — a determined script can still work around it in some cases, and it makes debugging your own component harder. Most components should use 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 .card vs .card conflicts.

  • Components can ship with sensible default styles without worrying they will "leak" and affect unrelated markup.

encapsulation-demo.html

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

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)
The full Web Components toolkit
<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 document.querySelector?

Yes

No — must query via the element's own shadowRoot

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 <slot>

Not every element can host a shadow root
A small set of elements (e.g. <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.