Learn the concept
Events
Events propagate through the DOM in three phases: capturing (top-down), target, and bubbling (bottom-up). stopPropagation() prevents the event from continuing to the next phase, while stopImmediatePropagation() also blocks remaining handlers on the current element.
When a DOM event fires, it doesn't just trigger on the target element — it travels through the entire DOM tree in a defined order called event propagation. Understanding these phases is essential for event delegation, handling complex UIs, and debugging unexpected behavior.
Capturing phase (top-down): The event travels from window down through the DOM tree toward the target element. Handlers registered with { capture: true } or true as the third argument to addEventListener fire during this phase.
Target phase: The event reaches the element that triggered it. Both capture and bubble handlers on the target element fire during this phase, in the order they were registered.
Bubbling phase (bottom-up): The event travels back up from the target to window. This is the default — handlers registered without capture: true fire during this phase.
Most events bubble, but some do not: focus, blur, mouseenter, mouseleave, load, scroll, and resize do not bubble. Use focusin/focusout as bubbling alternatives to focus/blur.
event.target — the element that actually triggered the event (e.g., the button that was clicked)event.currentTarget — the element the handler is attached to (e.g., the parent <div> listening via delegation)These are the same only when the handler is on the target element itself. In event delegation patterns, they differ.
event.stopPropagation() — stops the event from continuing to the next phase (no more capturing down or bubbling up), but other handlers on the same element still fire.event.stopImmediatePropagation() — stops propagation AND prevents any remaining handlers on the same element from executing.Neither affects preventDefault() — stopping propagation does not cancel the browser's default action.
event.preventDefault() — cancels the browser's default action (form submit, link navigation) but the event still propagates.event.stopPropagation() — stops the event from reaching other elements but the default action still happens.To do both, call both methods.
Event delegation leverages bubbling — attach one listener to a parent instead of many listeners to children:
ul.addEventListener('click', (e) => {
if (e.target.matches('li')) {
handleItemClick(e.target);
}
});This works because clicks on <li> elements bubble up to the <ul>. It also handles dynamically added items automatically.
// HTML: <div id="outer"><div id="inner"><button id="btn">Click</button></div></div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');
// Capture phase (top-down)
outer.addEventListener('click', () => console.log('outer capture'), true);
inner.addEventListener('click', () => console.log('inner capture'), true);
// Bubble phase (bottom-up) — default
btn.addEventListener('click', () => console.log('button target'));
inner.addEventListener('click', () => console.log('inner bubble'));
outer.addEventListener('click', () => console.log('outer bubble'));
// Clicking the button logs:
// 1. "outer capture" (capture, top-down)
// 2. "inner capture" (capture, continues down)
// 3. "button target" (target phase)
// 4. "inner bubble" (bubble, bottom-up)
// 5. "outer bubble" (bubble, continues up)Close dropdown menus by listening for clicks on the document body. Use stopPropagation on the dropdown itself to prevent clicks inside it from triggering the close handler.
Close modals when clicking the overlay background but not when clicking inside the modal content, using event.target comparison or stopPropagation.
Build a nested DOM structure where clicking any element highlights the propagation path in real-time, showing capture → target → bubble phases with timing.