๐Ÿซง JavaScript

Event Bubbling, Capturing & Delegation โ€” The Complete Picture

๐Ÿ“… Jul 4, 2026 โฑ 3 min read

Click a button inside a card inside a list โ€” three elements "hear" it. Understanding the journey unlocks delegation, the most useful DOM pattern.

The journey

Events travel down from document to the target (capturing), then back up (bubbling). Listeners default to the bubbling phase.

el.addEventListener("click", fn);              // bubbling (normal)
el.addEventListener("click", fn, true);        // capturing (rare)

e.target          // what was actually clicked
e.currentTarget   // where THIS listener is attached
e.stopPropagation()   // stop the journey here
e.preventDefault()    // different! stop the BROWSER action (submit, navigate)

Delegation โ€” one listener to rule them all

list.addEventListener("click", (e) => {
  const btn = e.target.closest("[data-action]");
  if (!btn || !list.contains(btn)) return;
  if (btn.dataset.action === "delete") removeItem(btn.closest("li"));
  if (btn.dataset.action === "edit")   editItem(btn.closest("li"));
});

Works for elements added later, uses one listener instead of hundreds โ€” it's how frameworks handle events internally.

Custom events

el.dispatchEvent(new CustomEvent("cart:add", { bubbles: true, detail: { id: 42 } }));
document.addEventListener("cart:add", (e) => console.log(e.detail.id));

Decoupled component communication โ€” vanilla JS's pub/sub.

โ† All Articles