Web Platform Feature of the Week logo

Web Platform Feature of the Week

Archives
Subscribe
August 25, 2025

Event Delegation And closest - Web Platform Feature of the Week

Mastering JavaScript event delegation and boosting performance, plus tips on the 'closest' method!

Another week, another web platform feature. I’ve realized I’ve been trending toward either introducing more than one feature or diving way deeper than I ever intended for this newsletter.

So this week, I’m reigning myself in and sticking to a single feature, with a little twist. Alongside the feature, I’m also introducing a technique called event delegation.

What is event delegation?

Most events in JavaScript bubble. With event delegation, we take advantage of that bubbling by attaching a single event listener to a common parent instead of attaching one to each individual element.

Did you know? JavaScript libraries such as React make heavy use of event delegation. When you register an event handler on an element in the DOM, React doesn’t attach it directly to that DOM node. Instead, it attaches a single handler per event type to the React application root (it used to be on the document node).

What are the benefits?

In short:

  • On large DOM trees, there’s a performance benefit to having fewer event listeners.

  • Dynamically added elements inside the parent automatically inherit the functionality without any extra work.

How do I implement it?

Normally, when adding event listeners, you’d attach the listener directly to the element where the action happens.

For example, let’s say we have an unordered list of movies. Each movie has:

  • An image (for example a movie poster)

  • A button

We might get all of the items, loop over them, and attach an event listener to each button:

const container = document.querySelector("#movie-list");
const items = Array.from(container.querySelectorAll(".movie"));

items.forEach((movie) => {
  const button = movie.querySelector(".action-button");
  button.addEventListener("click", () => {
    addToWatchlist(movie);
  });
});

This works, but if a user loads additional movies (say with a “Load more” button), you’d have to loop over the new items and re-attach listeners or attach them during each loop.

Here’s the same functionality using event delegation:

const container = document.querySelector("#movie-list");

container.addEventListener("click", (event) => {
  const { target } = event;

  if (target.tagName.toLowerCase() === "button") {
    addToWatchlist(target.parentElement);
  }
});

Now there’s just one event listener on the container. When a user clicks a button, the event bubbles up. The container intercepts it, checks the target, and if it’s a button, calls addToWatchlist. Newly added movie cards just work automatically.

What about closest?

Sharp eye! Right now we’re checking target.tagName, but that’s fragile. A click on an icon-only button with an SVG, or a button with SVG + text, might bubble up with a target that isn’t the button at all but an <svg> or <path>.

One workaround is CSS:

button * {
  pointer-event: none;
}

This tells the browser that any element inside a button shouldn’t trigger events. It works, but can have unintended side effects.

A more elegant solution is closest(). Since our buttons have a class action-button, we can write:

const container = document.querySelector("#movie-list");

container.addEventListener("click", (event) => {
  const { target } = event;

  if (target.closest(".action-button")) {
    addToWatchlist(target.parentElement);
  }
});

Now, if the user clicks anywhere inside the button (text, SVG, nested element) closest() walks up the DOM tree to see if the target has a parent matching .action-button. If it finds one, the handler runs. Otherwise, nothing happens.

closest() comes in handy far beyond this case. Check out the video walkthrough below for another example.

That’s it for this week. I hope you picked up something useful for your work or side projects. Comments or questions? Drop them on YouTube.


The Spotlight

This week I’m spotlighting Open Web Docs, the amazing team behind the documentation that powers MDN Web Docs and the wider open web. If you rely on MDN for your dev work, support them — their work is vital for keeping the web accessible, open, and developer-friendly. Learn more at openwebdocs.org.


You can find the code on GitHub and a live example is deployed here. You can also find the documentation for closest on MDN Web Docs.


Find value in the work I do? You can support the work by buying me a coffee. Thank you! ✌️

Read more:

  • Jul 14, 2025

    String.repeat() - Web Platform Feature of the Week

    Diving into the lesser-known JavaScript function 'String.repeat' with a credit card use case!

    Read article →
  • Aug 12, 2025

    Local and Session storage in the browser - Web Platform Feature of the Week

    Explore localStorage and sessionStorage techniques for data storage in browsers in this week's newsletter!

    Read article →
Don't miss what's next. Subscribe to Web Platform Feature of the Week:
GitHub
Website favicon
Bluesky
LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.