Codectionary / Developer documentation / JavaScript

Event Bubbling and Delegation

Most events "bubble" - after firing on the target element, they also fire on each ancestor element up to the document root, unless stopped with stopPropagation(). Event delegation takes advantage of bubbling by attaching a single listener to a parent element instead of many listeners on individual children, using event.target to identify which child was actually interacted with.

Syntax

parent.addEventListener("event", (e) => { if (e.target.matches(selector)) { } })

Examples

Understanding Bubbling

An event fired on a child also triggers listeners on its ancestors.

// <div id="outer"><button id="inner">Click</button></div>

document.querySelector("#outer").addEventListener("click", () => {
  console.log("Outer div clicked (via bubbling)");
});

document.querySelector("#inner").addEventListener("click", () => {
  console.log("Button clicked directly");
});

// Clicking the button logs both messages, in this order:
// "Button clicked directly"
// "Outer div clicked (via bubbling)"

Event Delegation

Using one listener on a parent to handle events from many current and future children.

// A single listener handles clicks on ANY .item, even ones added later
document.querySelector("#itemList").addEventListener("click", (event) => {
  if (event.target.matches(".item")) {
    console.log("Clicked item:", event.target.textContent);
  }
});

// Compare to attaching a separate listener to every .item individually,
// which would need to be redone whenever new items are added

stopPropagation()

Preventing an event from continuing to bubble up to ancestors.

document.querySelector("#inner").addEventListener("click", (event) => {
  event.stopPropagation(); // the outer div's listener will NOT fire
  console.log("Only this listener runs");
});

Best practices

  • Use event delegation for lists or grids where items are added/removed dynamically - one listener on the parent handles all current and future children
  • Use event.target (the actual element clicked) rather than event.currentTarget (the element the listener is attached to) when delegating
  • Reserve stopPropagation() for genuine cases where bubbling would cause a real problem - overusing it can break other legitimate listeners expecting the event to bubble
  • Combine delegation with matches() or closest() to reliably identify which specific child element triggered the event

At a glance

Purpose
Application logic and interaction
File extension
.js ยท .mjs
Runs in
Browsers and JavaScript runtimes
Usually used with
HTML, CSS and Web APIs

Specifications & further reading

Related JavaScript documentation