Shadow DOM vs regular DOM
The regular (light) DOM is the document's main tree, while Shadow DOM is a nested, isolated DOM tree inside an element that has its own styles, markup and behaviour and does not affect the outer document. In plain words: the light DOM is the "surface of the site", and Shadow DOM is the "insides of a component", hidden from the rest of the world.
Theory
TL;DR
- The light DOM is everything you write in HTML and see in DevTools; Shadow DOM lives inside a host element.
- The light DOM obeys global CSS, Shadow DOM is isolated from it.
- You reach the light DOM with
document.querySelector(), and the shadow tree only withshadowRoot.querySelector(). - Slots (
<slot>) exist only in Shadow DOM: they render light DOM content inside the component. - Shadow DOM is what you use for component encapsulation, style isolation and self-contained elements such as
<video>.
Quick example
const card = document.querySelector('.card');
const shadow = card.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button { color: blue; }
</style>
<button>Inner</button>
`;Now card has an inner Shadow DOM that is isolated from outside styles, is not part of the ordinary document tree (and with mode: 'closed' cannot be reached from outside at all), and manages its own elements. The global rule button { color: red; } will not repaint this button, because it lives in its own shadow tree.
Definitions: light DOM and Shadow DOM
| Type | What it is |
|---|---|
| Regular (light) DOM | The main tree of the HTML document: everything you write in HTML and see in DevTools. |
| Shadow DOM | A nested, isolated DOM tree inside an element (a component) that has its own styles, markup and behaviour and does not affect the outer document. |
For comparison, an ordinary card in the light DOM:
<div class="card">
<h2>Title</h2>
<button>Click</button>
</div>Everything here is visible in the main DOM, the button can be grabbed with document.querySelector('button'), and global styles apply to all of it: button { color: red; } makes the button red.
Key differences
| Characteristic | Regular DOM (light) | Shadow DOM |
|---|---|---|
| Where it lives | In the document tree | Inside a host element |
| Visible in DevTools | Yes | Yes, but hidden by default, enabled by "Show user agent shadow DOM" |
| Styling | Obeys global CSS | Isolated from global styles |
| Isolation | None, styles and JS affect each other | Full, an isolated context |
| Used in | Any HTML | Web Components, built-in elements (<video>, <input> and others) |
| Interaction | Directly via document.querySelector() | Only via shadowRoot.querySelector() |
| Slots | No | Yes (<slot> projects content from the light DOM) |
Structure and slots
<body>
<my-card> <!-- the host element -->
#shadow-root (shadow DOM)
<style>...</style>
<button>Click</button>
#light-dom (regular DOM)
<p slot="content">...</p>
</my-card>
</body>Here my-card is the host element, #shadow-root is its inner Shadow DOM, and outside elements can pass content in through <slot>. Note that the slotted <p> physically stays in the light DOM and is still subject to global styles, it is merely rendered at a chosen place in the shadow tree.
Access from code
From the light DOM you only see the component's "shell":
console.log(document.querySelector('my-card').innerHTML);
// Empty, because the insides are hidden in the Shadow DOMThe shadow tree is reachable only through a reference to its root:
const shadow = document.querySelector('my-card').shadowRoot;
console.log(shadow.querySelector('button')); // <button>Click</button>If the tree was created with mode: 'closed', shadowRoot is null and the inner nodes cannot be reached from outside at all.
Where you actually need it
Reach for Shadow DOM when you need to:
- encapsulate a component (web components, UI libraries);
- isolate styles so global CSS cannot break the look;
- build a reusable element, for example
<custom-slider>; - make a component self-contained, like
<video>or<select>.
A real-life example. In the regular DOM you redefined button styles across the whole site, and suddenly the buttons inside a component "broke", because the styles are global. With Shadow DOM, a component with its own styles is isolated, and no global button { color: red } touches it.
| Criterion | Regular DOM | Shadow DOM |
|---|---|---|
| Visibility | Available to all code | Hidden and isolated |
| Styles | Shared | Isolated |
| Usage | Everyday HTML | Components and built-in elements |
| Interaction | Through document | Through shadowRoot |
| Safety | No isolation | Encapsulated |
Common mistakes
- Looking for a component's inner nodes with
document.querySelector(): the selector does not cross the shadow boundary. - Assuming global CSS will "just work" inside a component: styles get in through the component's own
<style>, CSS custom properties or::part. - Confusing a slot with moving the node: slotted content stays in the light DOM, it is not copied into the shadow tree.
- Treating
closedas data protection: it only makes access harder, it provides no real security. - Forgetting about events: to make an event from the shadow tree bubble out you create it with
composed: true, and from the outside itstargetwill be the host.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.