What you'll be able to do

By the end of this page you should be able to explain bubbling (child → parent), name the capture phase (document → target), and use stopPropagation when a parent handler should not run.

Previous post: events and listeners.

Who this is for

  • People who clicked a button and wondered why the card handler ran too
  • Beginners who used stopPropagation as a hammer without knowing bubble
  • Anyone debugging “double action” UI

You can skip this if propagation phases are already clear. Come back when parent and child both listen for click.

Bubbling: inner to outer

<div id="card">
  <button id="go">Go</button>
</div>
document.getElementById("card").addEventListener("click", () => {
  console.log("card");
});
document.getElementById("go").addEventListener("click", () => {
  console.log("button");
});
// click button → "button" then "card"

Scenario A — overlay closes when background clicked. Parent listens; check event.target.

Scenario B — button should not trigger card action. event.stopPropagation() on the button handler (use sparingly).

Capturing: outer to inner (optional third argument)

card.addEventListener("click", onCapture, true); // capture phase

Scenario A — rare beginner need. Most UI code uses bubble only.

Scenario B — know it exists so DevTools “capture” wording is not alien.

Order: capture down → target → bubble up.

target vs currentTarget

function onCardClick(event) {
  console.log(event.target); // deepest element clicked
  console.log(event.currentTarget); // element that owns this listener (card)
}

Scenario A — click on icon inside button. target might be the <svg>; currentTarget is the element you bound.

stopPropagation vs preventDefault

MethodEffect
preventDefaultBlock browser default (link nav, form submit)
stopPropagationStop event from reaching other listeners on ancestors

They solve different problems.

A tiny practice file

Save as bubble.html.

<!DOCTYPE html>
<html>
  <body>
    <div id="outer" style="padding:2rem;background:#eee">
      Outer
      <button id="inner">Inner</button>
    </div>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById("out");
      function log(msg) {
        out.textContent += msg + "\n";
      }
      document.getElementById("outer").addEventListener("click", () => log("outer bubble"));
      document.getElementById("inner").addEventListener("click", function (e) {
        log("inner");
        e.stopPropagation();
      });
    </script>
  </body>
</html>

Mistakes I see a lot

1. stopPropagation everywhere — breaks delegation patterns later.

2. Assuming only one phase runs — bubble is the default mental model.

3. Comparing event.target to this without knowing currentTarget.

4. Nested clickable areas without a plan — buttons inside links, etc.

What to try before the next post

  1. Log bubble order on nested divs.
  2. Stop propagation on inner click; confirm outer silent.
  3. Log target.tagName vs currentTarget.id.
  4. Build bubble.html.

Next in this series: event delegation — one parent listener instead of a hundred item listeners.

Try this next outside the series

Browser JavaScript stops feeling isolated once you connect it to forms and component-oriented UI work.