Style Encapsulation
The shadow boundary
Section titled “The shadow boundary”The shadow root acts as a two-way style barrier:
- Styles inside the shadow root apply only to the shadow tree. They cannot leak out and affect elements in the light DOM.
- Styles in the light DOM (page CSS, external stylesheets) cannot pierce the shadow boundary and style elements inside the shadow tree.
This is the core value of Shadow DOM — you can build components that are truly self-contained, with no risk of accidental style collisions.
Shadow styles stay inside
Section titled “Shadow styles stay inside”Consider a shadow element that declares .box { color: red; } inside its shadow root:
class EncDemo extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = '<style>.box { color: red; font-weight: bold; }</style>' + '<div class="box">Red — shadow style</div>'; }}The .box { color: red; } rule has absolutely no effect on any .box element that exists outside this component.
Page styles stay outside
Section titled “Page styles stay outside”Now consider page-level CSS that targets .box:
/* page stylesheet */.box { color: blue; font-weight: bold; border: 2px solid blue;}This rule applies to every .box in the light DOM. It does not apply to the .box inside the shadow root of <enc-demo>. The shadow boundary blocks it.
Visual proof
Section titled “Visual proof”The demo below has two .box elements with the same class name. The one outside the custom element is styled blue by the page CSS. The one inside the shadow root is styled red by the shadow CSS. Neither style crosses the boundary.
Both div elements carry class="box", but they are styled independently because the shadow boundary separates them.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Style encapsulation (shadow boundary) | The component’s styles never accidentally clash with the page’s — safe to reuse anywhere | Outside theming can’t reach internals directly — you have to rely on CSS custom properties or ::part instead |
| No Shadow DOM (plain global CSS) | Free to style from outside, no extra API to expose | Class names risk colliding, and styles can leak in or out unintentionally as the project grows |
Common mistakes
Section titled “Common mistakes”- Expecting a plain page CSS selector to style inside a shadow root — writing
.my-widget .title { color: red }and wondering why it doesn’t work. The shadow boundary is deliberately designed to block exactly this. - Forgetting that class names inside and outside a shadow root are separate scopes — naming
.boxon both sides and worrying they’ll collide, when in fact they don’t, thanks to encapsulation. - Not knowing about the escape hatches — CSS custom properties and
::part— and giving up on Shadow DOM entirely when styling from the outside doesn’t just work, instead of designing a controlled custom-property API for theming.
💡 Real-world example
Salesforce Lightning (LWC) — uses style encapsulation so thousands of components from different teams don’t clash styles in a large enterprise app. (salesforce/lwc)