Skip to content
Laviyx Learn

Try

JavaScript course menu

Event Bubbling, Capturing and Event Delegation in JavaScript

Understand how events travel through the DOM: the capture, target and bubble phases, event.target versus currentTarget, stopPropagation, and event delegation, the pattern of handling events for many or dynamically added elements with one listener.

Intermediate5 min readUpdated
On this page

When you click a button inside a list inside a page, you have also clicked the list and the page. JavaScript models this by sending each event on a journey through the DOM. Understanding that journey unlocks one of the most useful patterns in front-end code: event delegation.

Bubbling#

After an event happens on an element (the target), it bubbles up through each ancestor in turn, triggering their listeners for the same event:

HTML
<div id="outer" style="padding: 16px; background: #fde7c8">outer
  <div id="inner" style="padding: 16px; background: #c9f0d3">inner
    <button id="btn">Click me</button>
  </div>
</div>
<pre id="log"></pre>
JavaScript
const log = document.querySelector("#log");
for (const id of ["btn", "inner", "outer"]) {
  document.querySelector(`#${id}`).addEventListener("click", (event) => {
    log.textContent += `${id} heard it (target: ${event.target.id})\n`;
  });
}
Result

Click the button: the log shows btn, then inner, then outer. All three listeners ran, and event.target was the button every time. Click the green area instead, and only inner and outer respond.

The three phases#

An event actually travels in three phases:

  1. Capture: from window down through the ancestors to the target.
  2. Target: at the element where it happened.
  3. Bubble: back up from the target to window.

Listeners run in the bubble phase by default. Pass { capture: true } as the third argument to run in the capture phase instead, which is rarely needed.

Most events bubble. A few do not, notably focus, blur, mouseenter and mouseleave; their bubbling versions are focusin, focusout, mouseover and mouseout.

target versus currentTarget#

PropertyMeaning
event.targetThe element where the event started (the deepest one clicked)
event.currentTargetThe element whose listener is running now

Stopping propagation#

event.stopPropagation() stops the event from travelling further up:

JavaScript
modal.addEventListener("click", (event) => {
  event.stopPropagation();   // clicks inside the modal do not reach the backdrop
});

Use it sparingly: other code (analytics, menus that close when you click outside them) may rely on events reaching the document. Do not confuse it with preventDefault(), which cancels the browser's default action but lets the event keep bubbling.

Event delegation#

Instead of adding a listener to every item, add one listener to their common parent and use event.target to find which item was clicked:

HTML
<ul id="todo">
  <li>Learn bubbling <button class="delete">✕</button></li>
  <li>Learn delegation <button class="delete">✕</button></li>
</ul>
<button id="add">Add item</button>
JavaScript
const list = document.querySelector("#todo");
let n = 1;

list.addEventListener("click", (event) => {
  const button = event.target.closest(".delete");
  if (!button || !list.contains(button)) return;   // the click was not on a delete button
  button.closest("li").remove();
});

document.querySelector("#add").addEventListener("click", () => {
  const li = document.createElement("li");
  li.innerHTML = `New item ${n++} <button class="delete">✕</button>`;
  list.append(li);
});
Result

Add some items and delete them: delete buttons on new items work too, even though no listener was ever attached to them. The list's single listener handles every click inside it.

Why delegation is useful:

  • Dynamic content: works for elements added later, with no extra code.
  • Fewer listeners: one listener instead of hundreds for long lists or tables.
  • Simpler cleanup: nothing to remove when items are deleted.

The key line is event.target.closest(".delete"): the click may land on an icon or text inside the button, so closest walks up to find the button itself (Selecting Elements).

Practice#

Practice exercise

Make a row of colour buttons (each with a data-colour attribute) inside one container, and use a single delegated listener on the container to set the background of a preview box to the clicked colour.

Show solution
HTML
<div id="palette">
  <button data-colour="tomato">Tomato</button>
  <button data-colour="seagreen">Sea green</button>
  <button data-colour="royalblue">Royal blue</button>
</div>
<div id="box" style="height: 60px; margin-top: 10px; border: 1px solid #ccc"></div>
JavaScript
document.querySelector("#palette").addEventListener("click", (event) => {
  const button = event.target.closest("button[data-colour]");
  if (!button) return;
  document.querySelector("#box").style.background = button.dataset.colour;
});
Result

Key takeaway#

Finished this lesson?