Concept · The browser

Event bubbling and delegation

Events travel up the tree, so one listener on a parent can handle every child — even ones added later.

3 minbubblingcapturingevent.targetstopPropagation

When you click a button inside a list item inside a list, the click doesn't stay on the button. A DOM event travels in three phases:

text
capture:  window → document → … → ul → li     (listeners with { capture: true })
target:   button
bubble:   li → ul → … → document → window      (normal listeners)

Most events bubble, so a listener on any ancestor hears clicks on its descendants. event.target is the element that was actually clicked. event.currentTarget is the element whose listener is running.

Delegation

Instead of attaching a listener to each item, attach one to the container and work out which item was clicked:

js
list.addEventListener('click', (event) => {
  const button = event.target.closest('button[data-action="delete"]')
  if (!button || !list.contains(button)) return
  removeItem(button.closest('li').dataset.id)
})
  • One listener instead of thousands, so there's less memory and setup.
  • Works for elements added later. New rows need no wiring, which matters when you render lists from data.
  • closest() handles clicks on an icon inside the button, where event.target is the <svg> or <span>.

React uses this approach internally. It attaches its listeners at the root and dispatches to your components.

Controlling propagation

  • event.stopPropagation() stops the event from reaching ancestors. Use it rarely, because it silently breaks delegated listeners, analytics and "click outside to close" handlers further up.
  • event.preventDefault() cancels the browser's default action, such as following a link or submitting a form. It doesn't stop bubbling, and the two are often confused.
  • Some events don't bubble, such as focus, blur, mouseenter and mouseleave. Use their bubbling versions, focusin, focusout, mouseover and mouseout, for delegation.

Accessibility

Delegate to real controls. A click listener on a <div> works for mouse users, but keyboard and screen-reader users can't reach it. A <button> gets focus, Enter and Space handling, and a role for free. See the accessibility tree.

Learn it in depth