Event Bubbling
Many events do not stop at the element where they start.
After the event runs on the original target, it can travel upward through parent elements. This is called event bubbling.
A Nested Example
<section id="panel">
<button id="save">Save</button>
</section>const panel = document.querySelector("#panel");
const button = document.querySelector("#save");
button.addEventListener("click", () => {
console.log("button listener");
});
panel.addEventListener("click", () => {
console.log("panel listener");
});If the user clicks the button, the output is:
button listener
panel listenerThe event starts at the button, then bubbles to the panel.
The Bubbling Path
For a click inside nested elements, the event may move through a path like this:
button -> section -> body -> html -> document -> windowYou can inspect this path with event.composedPath().
button.addEventListener("click", (event) => {
console.log(event.composedPath());
});This is useful for debugging complex UI.
target Stays the Same
During bubbling, event.target stays the original element where the event
started.
event.currentTarget changes depending on which listener is currently running.
panel.addEventListener("click", (event) => {
console.log(event.target); // the clicked element
console.log(event.currentTarget); // the panel
});This difference is important for event delegation.
Not Every Event Bubbles
Many common events bubble:
clickinputkeydownsubmitmouseover
Some events do not bubble in the same way:
focusblurmouseentermouseleave
For focus behavior that bubbles, use focusin and focusout.
form.addEventListener("focusin", (event) => {
event.target.classList.add("is-focused");
});Why Bubbling Is Useful
Bubbling lets parent elements observe events from their children.
const list = document.querySelector("#todo-list");
list.addEventListener("click", (event) => {
console.log("Something inside the list was clicked");
});This becomes very powerful when a list contains many buttons or items.
Instead of adding a listener to every child, you can add one listener to the parent. This pattern is called event delegation.
Bubbling and Dynamic Elements
Bubbling also helps with elements created after the page loads.
const list = document.querySelector("#messages");
list.addEventListener("click", (event) => {
if (event.target.matches(".delete-button")) {
event.target.closest(".message").remove();
}
});The listener is on the existing list. New delete buttons added later still work because their click events bubble to the list.
Debugging Bubbling
When an event seems to run more than once, bubbling may be involved.
Check:
- whether multiple ancestors have listeners
- whether the same listener was registered more than once
- what
event.targetandevent.currentTargetare - whether a delegated listener matches more elements than intended
document.addEventListener("click", (event) => {
console.log("target:", event.target);
console.log("current:", event.currentTarget);
});Summary
Event bubbling is the upward movement of an event from the original target through its ancestors.
It explains why parent listeners can respond to child events and makes event delegation possible.