How event.stopPropagation Works in the JavaScript DOM

This article provides an in-depth explanation of how event.stopPropagation() halts the event dispatch chain within the Document Object Model (DOM). It covers the three phases of standard event propagation, the internal mechanism the browser uses to cancel further traversal, and how this method differs from related DOM APIs like event.stopImmediatePropagation().

The Standard DOM Event Dispatch Chain

When an interaction occurs in the browser, the DOM does not execute the target element’s event listener in isolation. Instead, it dispatches the event through a defined three-phase path:

  1. Capturing Phase: The event travels downward from the window and document through the ancestor chain to the target element’s parent.
  2. Target Phase: The event reaches the actual element that triggered the interaction.
  3. Bubbling Phase: The event travels upward from the target element back through its ancestors to the window.

During this process, the browser builds an ordered propagation path—an array-like list of DOM nodes and their registered listeners.

How event.stopPropagation() Halts the Chain

When an event listener invokes event.stopPropagation(), it modifies the internal state of the Event object by setting an internal boolean flag (stopPropagationPath or canceled-flag in the W3C specification) to true.

Once this flag is set:

const parent = document.querySelector('#parent');
const child = document.querySelector('#child');

parent.addEventListener('click', () => {
  console.log('Parent clicked'); // Will not run if child stops propagation
});

child.addEventListener('click', (event) => {
  event.stopPropagation();
  console.log('Child clicked'); // Runs successfully
});

stopPropagation() vs. stopImmediatePropagation()

It is important to distinguish stopPropagation() from stopImmediatePropagation():

const button = document.querySelector('button');

button.addEventListener('click', (event) => {
  event.stopImmediatePropagation();
  console.log('First listener runs');
});

button.addEventListener('click', () => {
  console.log('Second listener will NOT run');
});

Practical Implications

Halting the dispatch chain is standard practice when building nested interactive components, such as a clickable link inside a clickable card, or an interactive modal overlay. Calling event.stopPropagation() ensures actions on child components do not unintentionally trigger handler logic on enclosing parent containers.