Event delegation
Handles events for many children using one parent listener.
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
See it in practice
Examples
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);
}
});Selected item: Item Name
Attaches one listener to the parent UL to handle clicks on any LI, including those added to the DOM later.
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);
}
});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.
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');
}
});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
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 */ } });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
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'.