DEV Community

Ishan Bagchi
Ishan Bagchi

Posted on

Event Bubbling and Capturing in JavaScript: The Complete Guide

Click a button inside a div inside a section, and suddenly JavaScript has a choice to make: which element should handle the event first?

That journey, where an event travels through layers of the DOM before and after reaching its target, is called event propagation. If you’re writing modern JavaScript, understanding this flow isn’t optional. It’s the difference between code that “just works” and code that’s clean, predictable, and easy to extend.

Let’s walk through it.


What is Event Propagation?

Every time you click, press a key, or interact with the DOM, the event doesn’t just “happen” on the element you touched. Instead, it follows a path through the DOM tree in a well-defined order.

Propagation has three phases:

  1. Capturing Phase (Event Capture)
    The event starts from the top (documenthtmlbody → …) and travels downward until it reaches the target.

  2. Target Phase
    The event lands on the actual element you interacted with (like a button or link).

  3. Bubbling Phase (Event Bubble)
    The event then travels back upward from the target, through its ancestors, until it returns to the top of the tree.

Here’s a visual that shows the entire journey:

Event propagation lifecycle

In this diagram:

  • The straight arrows down the middle represent the capturing phase, where the event travels from the top of the DOM (document, html, body, section, p) down to the actual target element (a).
  • The yellow box around the <a> element shows the target phase, the point where the event has reached the clicked element itself.
  • The curved red arrows on the right represent the bubbling phase, where the event moves back up through the ancestors of the target (psectionbodyhtmldocument).

Event Bubbling

Most events in JavaScript bubble by default. That means the event fires on the target element first, then climbs back up to its ancestors.

Example: Bubbling in Action

<div id="parent" style="padding: 20px; background: lightblue;">
  Parent Div
  <button id="child">Click Me</button>
</div>

<script>
  document.getElementById("parent").addEventListener("click", () => {
    console.log("Parent Div clicked");
  });

  document.getElementById("child").addEventListener("click", () => {
    console.log("Button clicked");
  });
</script>
Enter fullscreen mode Exit fullscreen mode

If you click the button:

  1. The button logs: “Button clicked”.
  2. Then the parent logs: “Parent Div clicked”.

That’s bubbling in action: the event bubbles up from the child to its parent.


Event Capturing

Capturing is the opposite direction: the event is “caught” on its way down before it even hits the target.

To listen during the capture phase, pass a third argument (true) to addEventListener.

Example: Capturing in Action

<div id="parent" style="padding: 20px; background: lightgreen;">
  Parent Div
  <button id="child">Click Me</button>
</div>

<script>
  document.getElementById("parent").addEventListener(
    "click",
    () => {
      console.log("Parent Div captured the click");
    },
    true // Enable capturing
  );

  document.getElementById("child").addEventListener("click", () => {
    console.log("Button clicked");
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Clicking the button now produces:

  1. “Parent Div captured the click” (captured on the way down).
  2. “Button clicked” (target phase).

Stopping Propagation

Sometimes you don’t want the event to keep traveling, like when a modal’s background is clickable, but you don’t want the click to close the modal if it happens inside the modal itself.

JavaScript gives you two tools:

  • event.stopPropagation() → halts further travel up or down the DOM tree.
  • event.stopImmediatePropagation() → does the same but also prevents other handlers on the same element from firing.

Example

child.addEventListener("click", (event) => {
  console.log("Button clicked");
  event.stopPropagation(); // Parent won’t log anything
});
Enter fullscreen mode Exit fullscreen mode

Why Does This Matter?

This isn’t just academic detail, it’s practical.

  • Event Delegation: Instead of adding a click listener to every li in a list, you attach one listener to the ul and let bubbling tell you which li was clicked. This saves memory and simplifies code.
  • Global Interception: Capturing lets you intercept events before they reach their targets, useful for logging, blocking certain actions, or building frameworks.
  • Cleaner Modals & Dropdowns: Controlling propagation prevents accidental clicks from closing UI components.

Propagation is the backbone of nearly every advanced UI pattern you’ve used on the web.


Recap

  • Events flow in three phases: capture → target → bubble.
  • Browsers default to bubbling unless you explicitly use capturing.
  • Use true in addEventListener to listen in the capturing phase.
  • Use stopPropagation() when you want to contain the event.

Closing Thought

Every time you click a button, JavaScript isn’t just running your handler,it’s walking that event through the DOM, like a courier making stops along a delivery route. Mastering event bubbling and capturing means you’re not just reacting to clicks; you’re controlling the journey itself.

Once you internalise this, features like event delegation, modals, and complex UI interactions stop feeling like hacks, and start feeling like extensions of the DOM’s natural flow.

Top comments (0)