All topics
DOMadvanced

Shadow DOM and Style Encapsulation

A native browser mechanism for attaching an isolated DOM subtree to an element, preventing styles and markup from leaking in either direction.

Shadow DOM attaches a separate subtree to an element. Styles and selectors do not cross the boundary in either direction.

Shadow DOM is like a hotel room with its own thermostat and decor completely separate from the building's shared HVAC and hallway decorations — the room's internal style choices don't leak into the hallway, and the hallway's paint job doesn't seep into the room, except for a few explicitly shared utilities (like the building's central water supply, similar to CSS custom properties) that both sides deliberately agree to share.

Key Concepts

1
    const shadow = el.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>p { color: red; }</style>
      <p>Inside</p>`;
2
That p rule applies only inside the shadow tree. A page-wide p { color: blue } does not reach in, and the red rule does not leak out.
pp { color: blue }
3
Which is the point. A widget embedded in an unknown page cannot be broken by that page's CSS, and cannot break it. That is not achievable with class-name conventions, only by convention.
4
    open    element.shadowRoot is accessible from outside
    closed  shadowRoot is null; only internal code can reach it
5
closed is not a security boundary — the reference can be captured — and it mostly makes debugging harder. open is the normal choice.
closedopen
6
Selectors stop at the boundary.
    document.querySelector('p')        // does not find the shadow p
    shadow.querySelector('p')          // does
7
Slots project light DOM content into the shadow tree.
    <!-- shadow -->
    <div class="card"><slot name="title"></slot><slot></slot></div>
8
    <!-- usage -->
    <my-card>
      <h2 slot="title">Title</h2>
      <p>Body</p>
    </my-card>
9
Slotted content stays in the light DOM — so page styles do apply to it, which is the useful exception.
10
Styling from outside is deliberately limited.
    my-card { display: block; }              the host element
    my-card::part(button) { }                exposed parts
    my-card { --card-bg: white; }            custom properties
11
    <!-- inside -->
    <button part="button">                   exposes it
    :host { background: var(--card-bg); }
12
Custom properties pierce the boundary, which makes them the standard theming mechanism for components.
13
:host and :host-context style the element itself from inside.
:host:host-context
14
Events need composed: true to escape, and their target is retargeted to the host — so an outside listener sees the component, not its internals.
composed: truetarget
15
The trade-offs. Global styles and resets do not apply, so a component starts from nothing. Forms need care, since a field inside shadow DOM is not automatically associated with an outer form. And tooling and testing are less convenient.
16
Which is why most applications use scoped CSS instead, and Shadow DOM is reserved for components that must survive being dropped into a page nobody controls.
17
What the interviewer is probing.1. "What does shadow DOM actually encapsulate?" Probing: the boundary. Stalls: "Everything." Moves up: styles and DOM structure — outside CSS does not reach in and inside styles do not leak out, though inherited properties still cross.
18
2. "What is the difference between open and closed mode?" Probing: the access. Stalls: "Security levels." Moves up: open exposes shadowRoot to script, closed returns null — which is a convenience barrier, not a security one.
19
3. "How do you let a consumer style part of it?" Probing: the escape hatches. Stalls: "You cannot." Moves up: CSS custom properties, which inherit through, and ::part for explicitly exposed elements.
20
4. "What is a slot?" Probing: composition. Stalls: "A placeholder." Moves up: a projection point where consumer-supplied content is rendered inside the shadow tree.