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:
Capturing Phase (Event Capture)
The event starts from the top (document
→html
→body
→ …) and travels downward until it reaches the target.Target Phase
The event lands on the actual element you interacted with (like a button or link).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:
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 (
p
→section
→body
→html
→document
).
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>
If you click the button:
- The button logs: “Button clicked”.
- 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>
Clicking the button now produces:
- “Parent Div captured the click” (captured on the way down).
- “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
});
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 theul
and let bubbling tell you whichli
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
inaddEventListener
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)