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
stopPropagationas 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
| Method | Effect |
|---|---|
preventDefault | Block browser default (link nav, form submit) |
stopPropagation | Stop 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
- Log bubble order on nested divs.
- Stop propagation on inner click; confirm outer silent.
- Log
target.tagNamevscurrentTarget.id. - 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.
- React forms without pain — see modern form handling after you understand the DOM version first
- HTML to JSX Converter — move tiny HTML snippets into React-style markup when you're ready