Three Phases of Event Propagation in JavaScript: Capturing, Target, and Bubbling Explained
The three phases of event propagation are capturing (downward from root to target), target (the actual element clicked), and bubbling (upward from target to root), processed in that exact order by the DOM event system.
When a user interacts with a nested element in the browser, the event does not simply fire on that single element. According to the lydiahallie/javascript-questions repository, the DOM processes every event through a specific three-phase lifecycle that determines the order in which event listeners execute. Understanding these phases of event propagation is essential for debugging complex UI interactions and controlling event flow in modern web applications.
What Are the Three Phases of Event Propagation?
The DOM event model defines three distinct phases that every event travels through when triggered on an element within a document hierarchy.
1. Capturing Phase (Event Capture)
During the capturing phase, the event travels downward from the outermost ancestor (typically the document or window object) through each parent element until it reaches the target element. This phase moves from the top of the DOM tree toward the specific element that was interacted with.
2. Target Phase
The target phase occurs when the event reaches the actual element that was clicked or interacted with. At this point, event listeners registered directly on the target element fire. According to the source code analysis of README.md lines 51-58 in the lydiahallie/javascript-questions repository, this is the middle phase where the event "lands" on its destination.
3. Bubbling Phase (Event Bubbling)
During the bubbling phase, the event travels upward from the target element back through each parent element, all the way to the document root. This phase propagates from the innermost element outward, allowing ancestor elements to respond to events that occurred on their descendants.
How Event Propagation Works in the DOM
The complete order of event propagation follows this strict sequence: capturing → target → bubbling. This means that if you have event listeners on both ancestor and descendant elements, the ancestors' capturing listeners will fire first, followed by the target's listeners, and finally the ancestors' bubbling listeners.
As documented in README.md lines 61-64 of the lydiahallie/javascript-questions repository, this three-phase model is the standard behavior for all DOM events (with rare exceptions like focus or blur which do not bubble).
Controlling Event Propagation with useCapture
You can control which phase an event listener executes in by using the useCapture parameter (or the options object) in addEventListener().
useCapture: false(default): The listener executes during the bubbling phase.useCapture: true: The listener executes during the capturing phase.
const parent = document.getElementById('parent');
const child = document.getElementById('child');
// Bubbling phase listener (default)
parent.addEventListener('click', () => {
console.log('Parent - Bubbling');
});
// Capturing phase listener
parent.addEventListener('click', () => {
console.log('Parent - Capturing');
}, true);
Practical Example: Capturing vs. Bubbling
Consider a nested DOM structure where you click on an inner element. The following code demonstrates exactly how the three phases of event propagation execute in order:
<div id="outer">
<div id="inner">
Click me
</div>
</div>
<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
// Capturing phase listeners
outer.addEventListener('click', () => console.log('outer – capture'), true);
inner.addEventListener('click', () => console.log('inner – capture'), true);
// Bubbling phase listeners
outer.addEventListener('click', () => console.log('outer – bubble'), false);
inner.addEventListener('click', () => console.log('inner – bubble'), false);
</script>
Expected console output when clicking the inner div:
outer – capture // Phase 1: Capturing (top-down)
inner – capture // Phase 1: Capturing reaches target
inner – bubble // Phase 2/3: Target and Bubbling start
outer – bubble // Phase 3: Bubbling continues upward
If you remove the true parameter from the capturing listeners, only the bubbling phase executes, resulting in:
inner – bubble
outer – bubble
Summary
- The three phases of event propagation are capturing (downward), target (the element itself), and bubbling (upward), processed in that exact sequence.
- Use
addEventListener()withuseCapture: trueto execute code during the capturing phase, or omit it (defaultfalse) to execute during bubbling. - The
lydiahallie/javascript-questionsrepository confirms this behavior inREADME.mdlines 51-58 and 61-64, documenting it as the standard DOM event model. - Understanding these phases allows you to control event delegation, prevent unwanted side effects, and optimize event handling in complex applications.
Frequently Asked Questions
What is the default phase for event listeners?
By default, all event listeners registered with addEventListener() execute during the bubbling phase. To capture events during the capturing phase instead, you must explicitly pass true as the third argument (or {capture: true} in the options object).
Can I stop event propagation between phases?
Yes, you can stop event propagation at any point using event.stopPropagation(). When called inside a listener, it prevents the event from continuing to subsequent listeners in the current phase or moving to the next phase. However, stopPropagation() does not prevent the target phase from executing listeners on the target element itself.
Why would I use the capturing phase?
The capturing phase is useful for intercepting events before they reach their target, particularly in event delegation scenarios where you want to handle events at a high level in the DOM tree before child elements process them. It is also used when you need to perform setup or validation logic before the target element's own event handlers execute.
Is event bubbling bad for performance?
Event bubbling itself is not inherently bad for performance, but inefficient use of it can cause issues. Attaching individual listeners to hundreds of child elements creates memory overhead, which is why event delegation (attaching a single listener to a parent and using bubbling to catch events from children) is actually a performance optimization. However, allowing events to bubble unnecessarily through many DOM levels when not needed can be prevented with stopPropagation() to optimize event handling chains.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →