Before reaching for a framework, it’s worth knowing that vanilla JavaScript’s DOM APIs handle a surprising amount of interactivity cleanly on their own.
const button = document.querySelector('.add-item');
const list = document.querySelector('.item-list');
button.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = 'New item';
li.className = 'item';
const removeBtn = document.createElement('button');
removeBtn.textContent = '×';
removeBtn.addEventListener('click', () => li.remove());
li.appendChild(removeBtn);
list.appendChild(li);
});
// Event delegation: one listener handles clicks on many, even future, children
list.addEventListener('click', (e) => {
if (e.target.matches('.remove-btn')) {
e.target.closest('li').remove();
}
});
Event delegation (attaching one listener to a parent instead of one per child) is the pattern that scales — it automatically handles elements added later without re-binding listeners, and uses far less memory than hundreds of individual event listeners.