JavaScriptDomIntermediate

Event delegation

Handles events for many children using one parent listener.

Review the syntaxStudy the examplesOpen the coding app
parent.addEventListener('click', (e) => {
  const target = e.target;
  // match selector and act
});

This static page keeps the syntax and examples indexed for search, while the coding app handles interactive exploration and saved references.

What it does

Overview

Handles events for many children using one parent listener.

Event delegation is a design pattern that leverages the bubbling phase of the DOM event flow. Instead of attaching individual event listeners to multiple child elements, a single listener is attached to a shared ancestor. When an event occurs on a child, it propagates (bubbles) up the DOM tree, where the parent listener intercepts it. This significantly reduces memory overhead and improves performance, especially in applications with large or frequently updating lists. It is also the most efficient way to handle events for dynamically generated elements, as the listener on the parent remains valid even when children are added or removed. Developers must distinguish between 'event.target' (the element that triggered the event) and 'event.currentTarget' (the element the listener is attached to). For nested child structures, 'Element.closest()' is the standard way to find the relevant container or interactive element. Note that certain events like 'focus', 'blur', 'mouseenter', and 'mouseleave' do not bubble; delegating these requires setting the 'useCapture' flag to true or using alternative events like 'focusin'.

Quick reference

Syntax

parent.addEventListener('click', (e) => {
  const target = e.target;
  // match selector and act
});

Inputs

Parameters

parentElement · Ancestor element that receives bubbled events.
selectorstring · CSS selector to match event targets (via closest).

See it in practice

Examples

1

Basic List Delegation

var list = document.querySelector('ul');
list.addEventListener('click', function(e) {
  if (e.target.tagName === 'LI') {
    console.log('Selected item: ' + e.target.textContent);
  }
});
Output:
Selected item: Item Name

Attaches one listener to the parent UL to handle clicks on any LI, including those added to the DOM later.

2

Handling Nested Elements with closest()

var container = document.querySelector('.button-group');
container.addEventListener('click', function(e) {
  var btn = e.target.closest('button');
  if (btn && container.contains(btn)) {
    console.log('Action: ' + btn.dataset.action);
  }
});
Output:
Action: save

Uses .closest() to find the button ancestor even if the user clicks a span or icon inside the button. IE not supported for .closest() without polyfill.

3

Delegation for Dynamic Elements

var wrapper = document.getElementById('todo-container');
wrapper.addEventListener('change', function(e) {
  if (e.target.type === 'checkbox') {
    e.target.parentElement.classList.toggle('completed');
    console.log('Status updated');
  }
});
Output:
Status updated

Handles state changes for checkboxes that are injected into the DOM via AJAX or client-side templates without needing to rebind listeners.

Debug faster

Common Errors

1

LogicError

Cause: Using e.target in nested structures without accounting for sub-elements (like an icon inside a button).

Fix: Use the .closest('selector') method to ensure you are interacting with the desired parent element regardless of which specific child node was clicked.

parent.addEventListener('click', (e) => { if(e.target.tagName === 'BUTTON') { /* Fails if click is on <i> inside button */ } });
2

TypeError

Cause: Attempting to delegate events that do not bubble, such as 'focus' or 'blur'.

Fix: Use the 'capturing' phase by passing 'true' as the third argument to addEventListener, or use events that bubble like 'focusin'.

form.addEventListener('focus', callbackFn); // Does not bubble, will not work for inputs.

Runtime support

Compatibility

BrowserAll modern browsersModern DOM

Source: MDN Web Docs

Common questions

Frequently Asked Questions

Handles events for many children using one parent listener.

parent: Ancestor element that receives bubbled events. selector: CSS selector to match event targets (via closest).

LogicError: Use the .closest('selector') method to ensure you are interacting with the desired parent element regardless of which specific child node was clicked. TypeError: Use the 'capturing' phase by passing 'true' as the third argument to addEventListener, or use events that bubble like 'focusin'.