When an event fires on a DOM element, it doesn't just trigger listeners on that element ā it travels through the DOM tree in two phases: capturing down from the root, then bubbling back up from the target. This lesson covers how that propagation works and how stopPropagation() lets a handler stop an event from continuing to travel.
1Bubbling & Capturing in JavaScript | Web Dev - In-Depth Guide Part 1
When an event occurs, it travels through the DOM. This is called propagation. Bubbling goes up, Capturing goes down.
element.addEventListener('click', (e) => {
e.stopPropagation();
console.log('Handled here only!');
});2Step-by-Step Breakdown
When an event occurs, it travels through the DOM. This is called propagation. Bubbling goes up, Capturing goes down.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Overzealous stopPropagation() Can Break Keyboard and Assistive Technology Event Handling
Some assistive technologies and global keyboard-navigation scripts rely on events bubbling up to the document to detect focus changes or key presses; calling stopPropagation() deep in a component tree can silently prevent those higher-level listeners from ever running, breaking screen reader announcements or keyboard shortcuts elsewhere on the page.
SEO Implications
- 1
Event Propagation Has No Direct SEO Impact But Affects Interactivity Metrics
Propagation itself isn't crawled or indexed, but relying on it incorrectly (e.g. a mis-scoped stopPropagation() breaking a mobile nav menu) can hurt user engagement signals and Core Web Vitals interaction metrics if users can't operate key UI elements.
Best Practices
Prefer Event Delegation Over Attaching a Listener to Every Child Element
Because events bubble, you can attach a single listener to a parent container and inspect `event.target` to determine which child was actually interacted with, instead of attaching a separate listener to every list item or button ā this scales far better for dynamically added elements.
Reach for stopPropagation() Sparingly and Document Why
Stopping propagation silently breaks any other code (yours or a third-party library's) that expected the event to keep bubbling, such as a modal's 'click outside to close' listener on the document. Only call it when you have a specific, well-understood reason, and prefer stopImmediatePropagation() only when you also need to stop sibling listeners on the same element.
Frequent Bugs
Clicking inside a modal dialog also triggers the page's 'click outside to close' handler attached to the document.
The click event bubbles from the element clicked inside the modal all the way up to the document listener. Call event.stopPropagation() on the modal's own click handler, or check whether the click target is contained within the modal element before closing it.
A capturing-phase listener (registered with `{ capture: true }`) doesn't seem to run at all.
Capturing listeners run during the trip down from the document to the target, before any bubbling-phase listeners ā if you're not seeing it fire, double check the third argument to addEventListener is actually `{ capture: true }` (or `true`) and not left at its bubbling-phase default.
Real-World Examples
Using Event Delegation for a Dynamic To-Do List
A to-do list added and removed items dynamically, and needed a delete button on each item to work without re-attaching a listener every time the list changed. A single click listener on the parent `<ul>` used event.target to detect which delete button was clicked, relying on bubbling.
list.addEventListener('click', (e) => {
if (e.target.matches('.delete-btn')) {
e.target.closest('li').remove();
}
});