Skip to content

Event Retargeting in Shadow DOM — Complete Guide

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Event Retargeting in Shadow Dom. We cover key concepts, practical examples, and best practices to help you master this topic.

Event retargeting adjusts event target properties when events cross shadow boundaries, making custom elements appear as a single logical unit to outside code.

What You'll Learn

  • What event retargeting is and why the browser does it
  • How event.target changes across shadow boundaries
  • The difference between composed and non-composed events
  • How to handle events from inside Shadow DOM

Why It Matters

Without retargeting, external code would see internal DOM nodes inside a shadow tree, breaking Encapsulation. Retargeting ensures a custom element appears as one unit to outside listeners.

flowchart LR
  A[User clicks inside Shadow DOM] --> B[Event fires on internal element]
  B --> C[Browser retargets event]
  C --> D[event.target becomes host element]
  D --> E[External listener sees host, not internal node]
  E --> F[Encapsulation preserved]

How Event Retargeting Works

When an event fires on an element inside a shadow tree and crosses the shadow boundary to reach a listener on the host or above, the browser adjusts event.target to point to the shadow host element. This prevents outside code from knowing the internal structure.

const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = '<button id="innerBtn">Click me</button>';
document.body.appendChild(host);

host.addEventListener('click', (event) => {
    // Without retargeting: event.target would be the button
    // With retargeting: event.target is the host
    console.log('Target:', event.target);
    console.log('Is host?', event.target === host);
});

// Clicking the button logs:
// Target: <div> (the host)
// Is host? true

Composed vs Non-Composed Events

Events in Shadow DOM have a composed property. Composed events cross shadow boundaries. Non-composed events stay inside the shadow tree.

// Most UI events are composed: click, keydown, focus, mouse events
// Some events are not composed: slotchange, DOMNodeInserted, custom events

class EventDemo extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = '<button id="btn">Test</button>';
        this.shadowRoot.getElementById('btn').addEventListener('click', (e) => {
            console.log('Composed:', e.composed); // true for click
        });
    }
}
customElements.define('event-demo', EventDemo);

// Outside listener sees retargeted target for composed events

Custom Events and Composed Flag

When you dispatch custom events from inside Shadow DOM, you must set composed: true for them to cross the boundary.

class MyComponent extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = '<button id="notify">Notify</button>';
        this.shadowRoot.getElementById('notify').addEventListener('click', () => {
            // Without composed: true, this event stays inside the shadow tree
            const event = new CustomEvent('my-action', {
                bubbles: true,
                composed: true,  // Required to cross shadow boundary
                detail: { message: 'Button was clicked' }
            });
            this.dispatchEvent(event);
        });
    }
}
customElements.define('my-component', MyComponent);

// External listener can now hear 'my-action' events
document.querySelector('my-component').addEventListener('my-action', (e) => {
    console.log('Received:', e.detail.message);
    // Output: Received: Button was clicked
});

Event Retargeting with Slots

When events come from slotted content (Light DOM projected into Shadow DOM), retargeting behaves differently. Slotted content lives in Light DOM, so its events do not get retargeted when heard from the host.

class SlotHost extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = '<slot></slot>';
        this.addEventListener('click', (e) => {
            // For slotted content: e.target is the actual slotted element
            // NOT the host. Slotted content is not retargeted.
            console.log('Target from slot click:', e.target);
        });
    }
}
customElements.define('slot-host', SlotHost);

// Usage: <slot-host><button>I am slotted</button></slot-host>
// Clicking the button shows the button as target, not the host

Common Mistakes

  1. Forgetting to set composed: true on custom events dispatched from Shadow DOM, causing them to be trapped inside the shadow tree.
  2. Expecting slotted content events to be retargeted like shadow tree events.
  3. Using event.target inside a shadow tree listener and assuming it refers to the host.
  4. Relying on event.composedPath() without understanding it includes internal nodes through all shadow boundaries.
  5. Dispatching events from inside a constructor before the element is connected to the DOM.

Practice Questions

  1. What does event retargeting do? It adjusts event.target to the shadow host when events cross shadow boundaries.
  2. Which events are composed by default? Most UI events like click, keydown, focus, and mouse events.
  3. How do you make a custom event cross shadow boundaries? Set composed: true in the event options.
  4. Why are slotted content events not retargeted? Because slotted content lives in Light DOM, not inside the shadow tree.

Challenge

Create a custom element with nested shadow trees. Dispatch a custom event from the innermost shadow tree with composed: false first, then with composed: true. Observe which listeners receive the event at each level.

FAQ

What is event retargeting in Shadow DOM?

Event retargeting is the browser's mechanism for adjusting event.target to the shadow host when an event crosses a shadow boundary, preserving encapsulation.

Does event retargeting apply to all events?

No. Only events that have their composed property set to true cross shadow boundaries with retargeting applied. Non-composed events stay inside the shadow tree.

How is event.composedPath() different from event.target?

event.composedPath() returns the full path of nodes an event travels through, including internal shadow tree nodes. event.target only shows the shadow host from outside.

Can I disable event retargeting?

No. Event retargeting is built into the browser and cannot be disabled. It is a fundamental part of Shadow DOM encapsulation.

Do focus events get retargeted?

Yes. Focus, blur, and focusin/focusout events are composed and get retargeted across shadow boundaries.

Mini Project

Build a button group component where each inner button dispatches a composed custom event. The host listens and logs which button was clicked. Then add an outside listener that receives the custom events and updates a status display.

What's Next

Lesson 12: Focus Management in Shadow DOM

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro