Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Use event delegation for dynamic content

Attach event listeners to stable parent elements rather than individual dynamic children to reduce memory usage and handle elements added to the DOM after page load.

Utilities
Quick take
Typical fix time 15 min
  • Attach one listener to a parent instead of many to individual children
  • Use event.target.closest() to identify the intended target element
  • Event delegation automatically works for elements added dynamically
  • Clean up listeners on the parent when the container is removed
Why it matters: Adding hundreds of individual event listeners for list items or table rows consumes significant memory and creates performance overhead. Dynamic content (items loaded via AJAX or rendered after user actions) won't have event listeners unless you re-attach them after each update — event delegation solves both problems at once.

Rule Details

Event delegation leverages the DOM's event bubbling mechanism — events fired on child elements bubble up to parent elements, where a single listener can handle them all.

Code Example

// ❌ Bad: one listener per item — 1000 items = 1000 listeners
document.querySelectorAll('.list-item').forEach(item => {
  item.addEventListener('click', handleItemClick)
})
 
// Also fails for dynamically added items:
const newItem = createItem()
list.appendChild(newItem)
// newItem has no click listener!

Why It Matters

Adding hundreds of individual event listeners for list items or table rows consumes significant memory and creates performance overhead. Dynamic content (items loaded via AJAX or rendered after user actions) won't have event listeners unless you re-attach them after each update — event delegation solves both problems at once.

Event Delegation Pattern

// ✅ Good: one listener on the parent handles all children
const list = document.getElementById('item-list')
 
list.addEventListener('click', (event) => {
  // Find the closest ancestor that matches our selector
  const item = event.target.closest('.list-item')
  if (!item) return // Click was outside an item (e.g., on the list itself)
 
  handleItemClick(item)
})
 
// Works for any item, including ones added later!
list.appendChild(createNewItem())

Using closest() for Nested Elements

<!-- The click target might be the icon inside the button -->
<ul id="todo-list">
  <li class="todo-item" data-id="1">
    <span class="todo-text">Buy groceries</span>
    <button class="delete-btn">
      <svg class="delete-icon">...</svg>  <!-- This is event.target -->
    </button>
  </li>
</ul>
const list = document.getElementById('todo-list')
 
list.addEventListener('click', (event) => {
  // closest() walks up the tree — works even if the SVG icon was clicked
  const deleteBtn = event.target.closest('.delete-btn')
  if (!deleteBtn) return
 
  const todoItem = deleteBtn.closest('.todo-item')
  const id = todoItem.dataset.id
  deleteTodo(id)
})

Multiple Action Types

const toolbar = document.getElementById('editor-toolbar')
 
toolbar.addEventListener('click', (event) => {
  const btn = event.target.closest('[data-action]')
  if (!btn) return
 
  const action = btn.dataset.action
  switch (action) {
    case 'bold': applyBold(); break
    case 'italic': applyItalic(); break
    case 'underline': applyUnderline(); break
  }
})

When NOT to Use Delegation

  • High-frequency events (mousemove, scroll) — add directly to specific elements
  • Non-bubbling events (focus, blur) — use capture phase ({ capture: true }) or focusin/focusout which do bubble
  • Single static element — delegation adds unnecessary complexity

Standards

  • Use MDN: JavaScript Guide as the standard for how this JavaScript pattern should behave in production, not just in a small local example.
  • Use web.dev: Learn JavaScript as the standard for how this JavaScript pattern should behave in production, not just in a small local example.

Verification

  1. Verify the behavior in the browser after the code change, not only in static analysis.
  2. Inspect DevTools Network or Performance panels when the rule affects loading or execution order.
  3. Test the primary user flow and one edge case triggered by the changed script path.
  4. Confirm the code still behaves correctly when the feature is delayed, lazy-loaded, or fails.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Find any code in this file that attaches the same event handler to multiple elements in a loop. Suggest event delegation as an alternative.

Fix

Auto-fix issues

Refactor these per-element event listeners to use event delegation on the common parent container.

Explain

Learn more

Explain event delegation, how event bubbling enables it, and when to use closest() vs target.

Review

Code review

Review scripts, client components, and browser execution paths related to Use event delegation for dynamic content. Flag exact imports, event handlers, runtime side effects, or blocking operations that violate the rule, and state how the change should be verified in the browser.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

MDN Event Delegationdeveloper.mozilla.orgTool

Rules that often go hand-in-hand with this one.

Prevent common memory leak patterns

Identify and avoid the most common JavaScript memory leak sources: forgotten event listeners, retained DOM references, closures holding large objects, and uncleared timers.

JavaScript
Minimize costly DOM read/write operations

Batch DOM reads and writes separately to avoid layout thrashing — the performance problem caused by alternating between reading and writing layout properties.

JavaScript
Debounce and throttle event handlers

Use debounce or throttle for high-frequency events like scroll, resize, and input to improve performance.

JavaScript

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385