Skip to content

Shadow DOM and Events — Complete Guide

DodaTech Updated 2026-06-28 1 min read

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

  1. Forgetting composed: true on custom events that need to cross boundaries
  2. Expecting event.target to be the internal element from outside
  3. Assuming non-composed events are completely blocked

Practice Questions

  1. What does event retargeting do? Changes the target to the host element when crossing the shadow boundary.
  2. 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