Skip to main content Accessibility Feedback

Event delegation and nested elements

Today, I wanted to talk about how to handle nested elements with event delegation.

Let’s dig in!

What’s event delegation!?

Event delegation is an approach where you attach your event listener to a parent element instead of directly on the element you’re listening to.

Then, in your callback function, you filter out any events that happen on elements you don’t care about.

Here, I’m listening for all click events in the document. If the element that triggered the click, the event.target, doesn’t have the .click-me class, I return early to end the function.

// Listen for clicks on the entire window
document.addEventListener('click', function (event) {

	// Ignore elements without the .click-me class
	if (!event.target.matches('.click-me')) return;
	
	// Run your code...

});

This approach is awesome because it means you can use one even listener to handle events on multiple elements.

It also means you don’t need to remove and attach event listeners when dynamically injecting elements. And, it’s actually better for performance than running the same listener on multiple elements.

The problem

One little “gotcha” with event delegation is when your HTML has nested elements in it.

Consider this button with the .click-me class on it.

<button class="click-me">
	<svg aria-label="thumbsup">...</svg>
	Like it!
</button>

Here, if you click the “Like it!” text, the JavaScript from our previous example will run as expected.

But, if you click the svg, the event.target.matches() method will return false, and your code won’t run.

Why? Because the event.target in this situation is the svg, not the parent button element.

How to fix it

It’s actually surprisingly simple.

If your HTML will have nested elements in it, use the Element.closest() method instead.

This method checks if the element it’s called on has or has a parent with the provided selector. If it finds a match, it returns the element.

// Listen for clicks on the entire window
document.addEventListener('click', function (event) {

	// Ignore elements without the .click-me class
	if (!event.target.closest('.click-me')) return;
	
	// Run your code...

});

Now, our event listener will behave as expected.