DOM events travel through the HTML hierarchy in three phases whenever a user clicks, types, or submits a form. Understanding capturing and bubbling lets you handle thousands of dynamic items cleanly using a single delegated parent listener.
flowchart TD
Doc["1. Capturing: document to parent"] --> Target["2. Target: clicked button"]
Target --> Bubble["3. Bubbling: parent back up to document"]
window and document toward the clicked element.event.target) where the interaction occurred.event.stopPropagation().Instead of attaching individual click listeners to 100 list buttons, attach one listener on the parent container and inspect event.target.closest("button"). Even buttons added dynamically later work immediately.
const app = document.getElementById("app");
if (app) {
app.innerHTML = `
<ul id="tag-list">
<li><button data-tag="js">Remove JS</button></li>
<li><button data-tag="dom">Remove DOM</button></li>
</ul>
`;
const list = document.getElementById("tag-list");
list.addEventListener("click", (event) => {
const btn = event.target.closest("button[data-tag]");
if (!btn) return;
console.log("Clicked tag button:", btn.dataset.tag);
});
// Programmatically dispatch a synthetic click to test delegation
list.querySelector("button")?.click();
}
event.target directly without .closest(): If a button contains an icon <span>, event.target may be the inner <span> instead of the <button>. Always match event.target.closest("button").event.stopPropagation(): Stopping bubbling breaks analytics trackers, dropdown close-on-outside-click handlers, and keyboard shortcuts on parent elements.event.target with event.currentTarget: event.target is the element clicked, whereas event.currentTarget is the element holding the listener.Most interaction events (click, input, keydown, submit) bubble, whereas focus, blur, and scroll do not bubble by default (focusin and focusout are their bubbling equivalents).
One parent listener replaces dozens or thousands of individual element closures, and you do not have to re-bind listeners whenever new list rows render.
preventDefault() and stopPropagation()?preventDefault() cancels the browser's built-in action (like following a link or reloading on form submit), while stopPropagation() stops the event from moving up or down the DOM tree.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
Experiment with the code from this lesson in our interactive playground.
You've completed this section! Take a quick 5-question quiz to check your understanding.