Shadow DOM and Events — Complete Guide
In this tutorial, you will learn about Shadow Dom and Events. We cover key concepts, practical examples, and best practices to help you master this topic.
Shadow DOM events are retargeted to the host element, and the composed option controls whether events propagate across shadow boundaries.
What You'll Learn
- How events are retargeted across shadow boundaries
- What event.composed means and how to use it
- How to dispatch events from shadow DOM components
- How to listen for events on shadow hosts
Why It Matters
Event handling across shadow boundaries is confusing without understanding retargeting and composition.
flowchart LR
A[Internal Event] --> B[Shadow Boundary]
B --> C[Retargeted to Host]
B --> D{composed?}
D -->|Yes| E[Propagate to Light DOM]
D -->|No| F[Stop at Boundary]
Event Retargeting
class RetargetDemo extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<button id="internalBtn">Click me</button>';
this.shadowRoot.getElementById('internalBtn')
.addEventListener('click', (e) => {
console.log('Event target:', e.target); // <button> inside shadow
console.log('Composed:', e.composed);
});
}
}
customElements.define('retarget-demo', RetargetDemo);
// External listener
document.querySelector('retarget-demo').addEventListener('click', (e) => {
console.log('External target:', e.target); // <retarget-demo> (retargeted!)
});
Expected output: Inside the shadow tree, e.target is the button. Outside, e.target is retargeted to the host element.
Composed Events
class ComposedDemo extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<button id="btn">Composed Demo</button>';
this.shadowRoot.getElementById('btn')
.addEventListener('click', (e) => {
console.log('Composed:', e.composed); // true for native events
});
}
// Dispatch custom composed event
doAction() {
this.shadowRoot.dispatchEvent(new CustomEvent('action', {
bubbles: true,
composed: true, // Cross shadow boundary
detail: { value: 42 }
}));
}
}
customElements.define('composed-demo', ComposedDemo);
// Listen on document for composed event
document.addEventListener('action', (e) => {
console.log('Received action:', e.detail);
});
Common Mistakes
- Forgetting composed: true on custom events that need to cross boundaries
- Expecting event.target to be the internal element from outside
- Assuming non-composed events are completely blocked
Practice Questions
- What does event retargeting do? Changes the target to the host element when crossing the shadow boundary.
- What does composed: true do? Allows the event to propagate across shadow DOM boundaries.
Mini Project
Build a component tree with nested shadow DOMs. Dispatch events from the deepest level and observe how they retarget and propagate at each boundary.
What's Next
Lesson 7: Slot Basics
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro