Using jQuery .on() for Event Delegation with Dynamic Elements
Learn how to use jQuery’s .on() method with a selector to delegate events to dynamically added elements, reducing listener count and avoiding missed interactions.
03 Apr 2026, 13:29 UTC

Quick answer
Attach a single event listener to a stable parent element using .on(event, selector, handler). The listener will fire for existing children that match selector and for any matching children added later (e.g., via AJAX or script).
How it works
When an event occurs on a DOM element, it bubbles up through its ancestors unless the event type is non‑bubbling (e.g., focus). jQuery’s .on() method can intercept that bubbling event at a delegated parent, check the event’s target against the supplied selector, and invoke the handler only if there is a match. Because only one listener is placed on the parent, memory usage stays low even when many child elements exist or are added later.
Worked example
Assume a page contains an empty list and a button that adds new list items via jQuery.
<!-- HTML -->
<ul id="itemList"></ul>
<button id="addBtn">Add item</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
// Delegated click handler: one listener on the UL
$('#itemList').on('click', '.item', function () {
console.log('Clicked item:', this.textContent);
$(this).toggleClass('highlight');
});
// Function to add a new item (simulating AJAX or dynamic UI)
$('#addBtn').on('click', function () {
const $newItem = $('').addClass('item').text('Item ' + ($('.item').length + 1));
$('#itemList').append($newItem);
});
</script>
Where to run: place the snippet in a regular HTML file and open it in a browser; no special server permissions are needed beyond loading the page.
Expected checks:
- Clicking any
.item(including those added after page load) logs a message and toggles the highlight class. - Open Chrome DevTools → Elements → select
#itemList→ Event Listeners tab; you should see only oneclicklistener attached to the UL, not to each LI. - If you inspect the
documentelement, you will not find a click listener for this feature (unless you added one elsewhere).
Limits and common mistakes
Performance considerations
Using a very high‑level parent such as $(document) for every delegated event forces the browser to evaluate the selector on every bubbling event throughout the DOM. In large documents this can add measurable latency. Prefer the nearest static ancestor that reliably contains the target elements.
Non‑bubbling events
Events like focus, blur, mouseenter, and mouseleave do not bubble. The standard .on(event, selector, handler) pattern will not work for them. For focus/blur you can use the focusin and focusout events (which do bubble) or attach listeners directly to the elements when they are created.
Overly broad selectors
A selector like '*' or 'div' causes jQuery to test the target against a large set of candidates on each event, increasing CPU work. Keep selectors as specific as practical (e.g., '.item' or 'button.save').
Typical pitfalls
- Omitting the selector argument:
$('#parent').on('click', handler)binds directly to the parent, not to its children, so dynamically added children will not trigger the handler unless they are the parent itself. - Using the wrong event type (e.g., binding
clickbut expectingchangeon inputs). Verify the event matches the user interaction. - Assuming delegation works for custom events that do not bubble; you must trigger them with
triggerHandleror ensure they bubble.
Practical verification
After adding new elements, open the console and click them. If the handler runs, delegation is successful. Additionally, you can run:
// Returns the number of click listeners on the delegated parent
jQuery._data($('#itemList')[0], "events").click?.length || 0
In a clean setup this should report 1. Note that accessing jQuery’s internal data structures is for debugging only and may change in future versions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.