Event Bubbling and Delegation

Grade 12, page 6 of 9. A two-minute read.

Here's a fascinating thing about events. When you click a button, you're not just clicking the button. You're also clicking whatever it's inside of, and whatever that's inside of, all the way up to the document. After an event fires on the element you clicked, it travels upward through all its ancestors, like a bubble rising through water. This is called event bubbling.

Events bubble up from the clicked element through each of its ancestors
HTML
<div id="outer">
  <button id="inner">Click me</button>
</div>
JavaScript
document.querySelector("#inner").addEventListener("click", () => {
  console.log("1. Button clicked");
});
document.querySelector("#outer").addEventListener("click", () => {
  console.log("2. Div clicked (the event bubbled up!)");
});
document.addEventListener("click", () => {
  console.log("3. Document clicked (still bubbling!)");
});

Click the button, and all three messages appear, in that order: button first, then the div, then the document.

(If you ever need to stop an event from bubbling any further, you can call event.stopPropagation(). But you'll rarely need it.)

Event delegation

Bubbling might seem like a weird curiosity, but it enables a really powerful trick called event delegation.

Imagine you have a to-do list, and each item has a delete button. You could add a separate click listener to every single button. But what about new items that get added later? They wouldn't have listeners, so you'd have to remember to add one each time. That gets messy.

Instead, put one listener on the parent list. Because clicks bubble up, any click on any button inside the list will reach the parent. Then use event.target to figure out what was actually clicked:

HTML
<ul id="todo-list">
  <li>Buy plantain <button class="delete">❌</button></li>
  <li>Call Grandma <button class="delete">❌</button></li>
  <li>Learn JavaScript <button class="delete">❌</button></li>
</ul>
JavaScript
const list = document.querySelector("#todo-list");

list.addEventListener("click", (event) => {
  if (event.target.classList.contains("delete")) {
    const item = event.target.closest("li");
    item.remove();
  }
});

One listener handles every delete button, including any buttons for items that are added later. When a click happens anywhere in the list, we check: was it on a .delete button? If so, we use closest("li") to climb up the family tree to the list item that contains the button, and remove it.

Think of it like thisThe receptionist

Event delegation is like having one receptionist at the front desk of an office building, instead of a separate doorman for every single office. Every visitor passes through the lobby, and the receptionist directs them to the right place. When a new office opens, you don't need to hire a new doorman. The receptionist already handles it.

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning