With event delegation, event.target is the deepest element clicked. If a button contains an SVG or icon, comparing the target directly with the button fails. Walk upward with closest() and verify that the match belongs to the delegated container.
Last updated: November 1, 2026.
const taskList = document.querySelector('#task-list');
taskList.addEventListener('click', (event) => {
const target = event.target instanceof Element ? event.target : null;
const button = target?.closest('button[data-action="delete"]');
if (!button || !taskList.contains(button)) return;
button.closest('li')?.remove();
});The handler works whether the original click lands on the button, an SVG, a path inside the SVG, or another nested element. The containment check prevents a match outside the list from being handled accidentally.
Understand target and currentTarget
MDN’s event-bubbling guide distinguishes target, where the event began, from currentTarget, where the listener is attached. In this example, currentTarget is always the list, while target may be the icon.
The closest() method checks the element itself and then its ancestors until the selector matches. That makes it appropriate for buttons with changing internal markup.
Use stable action markers
Use a data-action attribute instead of matching visible text or decorative class names. If several actions share the same container, read button.dataset.action and route only known values. Keep the actual operation explicit; a click should not execute arbitrary function names stored in markup.
Use a real <button type="button"> so keyboard activation and accessibility behavior are preserved. Test clicks on the button label, every nested icon, and dynamically inserted rows. Setting pointer-events: none on an icon can hide this symptom, but closest() keeps the handler correct when the button markup changes.
Continue with calling a route from a button, preventing duplicate submissions, and detecting button state changes.