<div class="problem-buttons">
  <h2>The Problem: Not Delegating Events Means Rebinding</h2>
  <button class="add">Add Button</button>
  <button class="btn template">Button</button>
</div>

<div class="jquery-buttons">
  <h2>jQuery Event Delegation</h2>
  <button class="add">Add Button</button>
  <button class="btn template">Button</button>
</div>

<div class="vanilla-js-buttons">
  <h2>Vanilla JS Event Delegation</h2>
  <button class="add">Add Button</button>
  <button class="btn template">Button</button>
</div>

<div class="web-component-buttons">
  <h2>Web Component "Event Delegation"</h2>
  <button class="add">Add Button</button>
  <toggle-button class="template"></toggle-button>
</div>
@import "https://unpkg.com/open-props";

toggle-button {
  --wrapperBackground: red;
}

button {
  font-weight: var(--font-weight-7);

  padding-inline: var(--size-3);
  padding-block: var(--size-1);

  color: var(--blue-9);
  border: var(--border-size-2) solid var(--blue-5);
  background-color: transparent;
  border-radius: var(--radius-2);
  &.active {
    background-color: var(--red-5);
  }
}

body {
  background: var(--gray-9);
  font-family: var(--font-sans);
  display: grid;
  gap: 2rem;
  margin: 0;
  padding: var(--size-fluid-3);
  > div {
    background: var(--gray-1);
    border-radius: var(--radius-2);
    padding: var(--size-fluid-3);
    box-shadow: var(--shadow-2);

    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
  }
}

h2 {
  width: 100%;
  margin: 0 0 1rem 0;
}
// General Problem
const originalButtons = document.querySelectorAll(".problem-buttons .btn");
originalButtons.forEach((button) => {
  button.addEventListener("click", () => {
    button.classList.toggle("active");
  });
});

// Old skool jQuery event delegation
$(document).on("click", ".jquery-buttons .btn", function () {
  $(this).toggleClass("active");
});

// This is how Vanilla JS event delgation works.
document.querySelector(".vanilla-js-buttons").addEventListener("click", (e) => {
  if (e.target.matches(".vanilla-js-buttons .btn")) {
    e.target.classList.toggle("active");
  }
});

// This is how Web Components work, which sorta naturally has event delegation.
customElements.define(
  "toggle-button",
  class WebComponentButton extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `<button class="btn">Button</button>`;
      const button = this.querySelector(".btn");
      button.addEventListener("click", () => {
        button.classList.toggle("active");
      });
    }
  }
);

// Button Duplicator
const addButtons = document.querySelectorAll(".add");
addButtons.forEach((button) => {
  button.addEventListener("click", () => {
    const templateButton = button.parentElement.querySelector(".template");
    const newButton = templateButton.cloneNode(true);
    button.parentElement.appendChild(newButton);
  });
});

// other ideas: inline click handles like JSX but native.

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js