Loading...
Loading...
Events don't just fire on one element. They travel through the DOM.
The Document Object Model is how JavaScript reads and changes web pages. Selecting elements, modifying content, handling events, validating forms, and persisting data in storage are the core skills of frontend development.
Learn Event Propagation in JavaScript — covering Three Phases, Bubbling, Capturing, stopPropagation with interactive visual examples and step-by-step explanations.
JavaScript in the browser is all about the DOM — the live tree of HTML elements your code can select, modify, and respond to. Click handlers, form validation, dynamic content updates, and local storage all build on DOM APIs.
This module takes you from querySelector to full user interaction patterns. You will learn event delegation, form handling, and how to persist state in localStorage — the skills every frontend developer uses daily.
Understanding event propagation is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
Every DOM event goes through three phases.
Click on <button> inside <div> inside <body>:
// By default, addEventListener listens during BUBBLING: div.addEventListener("click", handler); // phase 3 // To listen during CAPTURING: div.addEventListener("click", handler, true); // phase 1 // or: { capture: true } // Check which phase in the handler: element.addEventListener("click", (e) => { e.eventPhase; // 1=capturing, 2=target, 3=bubbling });
Events bubble UP from the target to the root.
// HTML: // <div id="outer"> // <div id="inner"> // <button id="btn">Click me</button> // </div> // </div> outer.addEventListener("click", () => console.log("outer")); inner.addEventListener("click", () => console.log("inner")); btn.addEventListener("click", () => console.log("btn")); // Click the button → output: // "btn" (target) // "inner" (bubbles up) // "outer" (bubbles up) // The event "bubbles" from child to parent to grandparent...
💡 Bubbling is why event delegation works — a click on a child element also fires on all its ancestors.
Events travel DOWN from root to target before bubbling.
// Capture phase (down) + bubble phase (up): outer.addEventListener("click", () => console.log("outer capture"), true); inner.addEventListener("click", () => console.log("inner capture"), true); btn.addEventListener("click", () => console.log("btn")); inner.addEventListener("click", () => console.log("inner bubble")); outer.addEventListener("click", () => console.log("outer bubble")); // Click button → output: // "outer capture" ↓ (capturing down) // "inner capture" ↓ // "btn" ● (target) // "inner bubble" ↑ (bubbling up) // "outer bubble" ↑
Capturing is rarely used. The main use case is intercepting events before they reach the target — like a global click handler that blocks certain actions.
Stop the event from traveling further.
// stopPropagation — prevent bubbling: btn.addEventListener("click", (e) => { e.stopPropagation(); console.log("btn clicked"); // Parent handlers will NOT fire! }); outer.addEventListener("click", () => { console.log("This won't run if btn is clicked"); }); // stopImmediatePropagation — also stops OTHER handlers // on the SAME element: btn.addEventListener("click", (e) => { e.stopImmediatePropagation(); console.log("First handler"); }); btn.addEventListener("click", () => { console.log("This won't run either!"); });
stopPropagation
Stops bubbling/capturing
Other same-element handlers still fire
stopImmediatePropagation
Stops ALL propagation
Even same-element handlers stop
When propagation knowledge matters.
// 1. Modal close — click outside to close: overlay.addEventListener("click", () => closeModal()); modal.addEventListener("click", (e) => { e.stopPropagation(); // don't close when clicking INSIDE modal }); // 2. Dropdown menu — close on outside click: document.addEventListener("click", (e) => { if (!dropdown.contains(e.target)) { dropdown.classList.add("hidden"); } }); // 3. Prevent link navigation in a card: card.addEventListener("click", () => openCard()); card.querySelector("a").addEventListener("click", (e) => { e.stopPropagation(); // let link work normally });
Common questions about event propagation.