Free tools Windows power users keep installed
One-click scans. No signup required.
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Event bubbling lets an event that starts on a nested element travel up through its ancestors, so a parent can respond to clicks on its children. Event delegation uses that behavior: attach one listener to a shared parent, then identify which descendant was activated. It is a practical pattern for repeated controls and dynamically added content—but it depends on the event bubbling to that parent.
What event bubbling means
Browser events are provided by the Web Platform, rather than being a feature of the JavaScript language itself. When an event propagates through the DOM, it moves through three phases: capture travels from ancestors toward the target, the target phase runs listeners on the target, and bubbling travels back up through ancestors. MDN’s event bubbling guide walks through these phases.
For example, if a button is inside a list, a click on the button can also reach a listener on the list. With addEventListener(), the default is to listen during the bubbling phase, unless you set the capture option to true. See MDN’s addEventListener() reference for the option and its default.
Recommended Free Tools
How event delegation works
Instead of attaching a separate listener to every repeated control, delegation attaches a listener to their shared ancestor. The handler checks the event’s target to find the relevant descendant and then runs the appropriate action. This can also cover descendants added after the listener was attached, provided their event bubbles to the delegated ancestor.
#1 Best Overall
Here is a todo-list example. The selector finds the button even when the click lands on an icon or span inside it; the containment check keeps the match inside this list.
const list = document.querySelector(".todo-list");
list.addEventListener("click", (event) => {
const button = event.target.closest("button[data-action]");
if (!button || !list.contains(button)) return;
if (button.dataset.action === "remove") {
button.closest("li")?.remove();
}
});
Adapt the selectors and action to your markup. The list element must exist when the listener is attached, and the click event must bubble to it.
Rank #2
Choose the right event target
event.target is where the event originated; event.currentTarget is the object whose listener is currently running. In a delegated handler, the target may be a nested icon rather than the button you want to act on. Use a selector such as closest() to find the intended control, and constrain the match to the delegation root when needed. Use currentTarget when you need to refer to the root whose listener is executing. MDN documents these properties in its Event reference.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesDelegation or a listener on each element?
Neither approach is automatically best. Choose based on the structure and lifecycle of the controls, and on which approach keeps the behavior easiest to understand.
| Approach | Works well when | Considerations |
|---|---|---|
| Event delegation | Many repeated descendants share a parent, or new descendants need the same behavior after insertion. | The event must bubble to the delegated ancestor, and the handler must reliably identify the intended descendant. |
| Direct listeners | A control is small, isolated, or easier to understand with its behavior attached directly. | Each relevant element needs its own listener, including elements added later if they need the behavior. |
Delegation can centralize code, but the cited documentation does not establish that it is always faster. Prefer the clearest design for the number and lifecycle of your controls.
When to use capture, bubbling, and propagation controls
Use capture when a handler must run on the way down
Set { capture: true } in the addEventListener() options when the listener needs to run during capture, before the event reaches its target. For typical delegated interactions, the default bubbling listener is usually the relevant choice. The timing behavior is described in MDN’s addEventListener() documentation.
Rank #4
Do not confuse stopping propagation with canceling an action
stopPropagation() prevents the event from continuing to propagate, but it does not cancel a browser default action such as following a link. If the event is cancelable and you need to prevent its default action, call preventDefault(). stopImmediatePropagation() also prevents other listeners on the same element from running. MDN explains these distinctions in the stopPropagation() reference and Event reference.
Avoid stopping propagation as a routine fix for an unexpected handler: doing so can keep unrelated ancestor listeners from receiving the event.
Best Value
Events and Shadow DOM boundaries
Delegation across a Shadow DOM boundary needs more than a listener on an outer ancestor. The event must be both bubbling and composed to propagate through that boundary. For custom events, check their bubbles and composed settings; neither behavior should be assumed. MDN describes boundary crossing in its Event.composed reference.
event.composedPath() can help inspect the route an event took. However, a path observed outside a closed shadow root does not reveal that root’s internal nodes. The Event reference documents composedPath() and related event properties.
Quick Recap
A quick debugging checklist
- Confirm the event actually bubbles to the ancestor where the listener is attached.
- Check whether the listener is using the default bubbling phase or was registered with capture enabled.
- Inspect
event.targetandevent.currentTarget; the target may be a nested element inside the control. - For Shadow DOM, check both
bubblesandcomposed, and usecomposedPath()with closed-root visibility in mind. - When behavior seems canceled, distinguish propagation from the browser’s default action:
stopPropagation()andpreventDefault()do different jobs.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

