Learning Objectives
- Understand Shadow DOM and light DOM and differences
- Learn when to use Shadow DOM vs light DOM
- Compare open vs closed shadow roots
- Explore ideas to poke into the shadow with styles
What is Shadow DOM?
Shadow DOM provides encapsulation for web components by creating a separate DOM tree that's attached to an element but isolated from the main document tree. The main advantage of this is to protect the markup, style, and script (mostly events) from other parts of the page.
Core Features
- Style Isolation: Styles don't leak out and only somewhat in (with effort)
- DOM Encapsulation: Internal structure is hidden
- Scoped Events: Some events are scoped to shadow tree
Benefits of Shadow DOM
✅ Style Encapsulation
CSS defined inside shadow DOM doesn't affect the rest of the page, and external CSS doesn't affect the shadow DOM.
/* Inside shadow DOM */ .button { color: blue; /* Won't conflict with page .button */ } ✅ Implementation Hiding
Internal DOM structure is hidden from external JavaScript, protecting implementation details.
// External code can't access internals element.querySelector('.internal'); // null // Must use shadowRoot element.shadowRoot.querySelector('.internal'); // ✓
✅ Composition via Slots
Slots enable flexible content projection while maintaining encapsulation.
<my-card> <span slot="title">Card Title</span> <p>Card content</p> </my-card> ✅ Scoped Events
Some events are retargeted to maintain encapsulation boundaries.
// Event target is the host element // not the internal element that was clicked element.addEventListener('click', (e) => { console.log(e.target); // <my-element> });
Limitations of Shadow DOM
Key Limitations
- Styling Complexity: External styling requires CSS custom properties or parts
- Form Participation: Shadow DOM elements don't automatically participate in forms
- Accessibility Challenges: Some screen readers and tools struggle with shadow boundaries
- Global Styles: Utility classes and global styles don't penetrate shadow boundaries
- Third-party Integration: Libraries that rely on global selectors won't work
- Bot Considerations: Content in shadow DOM may not be indexed as effectively or worked with bots, though arguably most JavaScript driven content can suffer this problem as well
Example: External Styling Limitation
Open vs Closed Shadow DOM
When attaching a shadow root, you must specify whether it's open or closed.
✅ Open (Recommended)
constructor() { super(); this.attachShadow({ mode: 'open' }); } // Accessible from outside element.shadowRoot // Returns shadowRoot Pros:
- Debuggable in DevTools
- Testable
- Extensible
- Standard practice
❌ Closed (Rarely Needed)
constructor() { super(); this.attachShadow({ mode: 'closed' }); } // Not accessible from outside element.shadowRoot // null Cons:
- Harder to debug
- Can't test easily
- Not actually secure
- Limits flexibility
Demo: Shadow DOM vs Light DOM Comparison
Compare the same message banner component implemented with and without Shadow DOM to see the practical differences.
Light DOM Implementation
Shadow DOM Implementation
Live Comparison
Both banners render the same content through the same type attribute. The page in the frame below also carries one global rule targeting .banner-content — watch which banner it reaches.
When to Use Shadow DOM
Choosing between Shadow DOM and light DOM depends on your specific use case and requirements.
✅ Use Shadow DOM When:
- Style isolation is critical
- Building reusable component libraries
- Need implementation hiding
- Want slot-based composition
- Building standalone widgets
- Creating design system components
❌ Avoid Shadow DOM When:
- Need deep CSS customization
- Working with form elements
- Require global styles to apply
- Building application-specific components
- SEO is critical (SSR)
- Need maximum accessibility
Styling Strategies for Shadow DOM
When using Shadow DOM, you need strategies to allow customization while maintaining encapsulation.
1. CSS Custom Properties (Variables)
2. CSS Parts (::part)
3. Host Context (:host-context) — Chromium only
The three deliberate holes
Encapsulation cuts both ways. The page cannot reach in, which is the point, and is also the problem the first time somebody wants your button in their brand color. The platform's answer is not to weaken the boundary but to let the component open specific holes in it.
Best Practices
Next Steps
In Step 5: Component lifecycle, we'll explore:
- All five lifecycle callbacks, including the rarely-seen
adoptedCallback - Constructor rules and restrictions
- Resource management: timers, observers, and event listeners
- Memory leak prevention, and one
AbortControllerthat removes every listener at once - Handling elements that connect and disconnect more than once