Using jQuery .on() for Dynamic Elements: A Practical Guide to Event Delegation
Learn how jQuery’s <code>.on()</code> method lets you bind events to dynamic elements efficiently. This guide covers the problem, delegation mechanics, a step‑by‑step example, trade‑offs, and actionable steps for your projects.
08 Mar 2026, 06:32 UTC

Why you’re still binding events the old way
Most beginner tutorials show you how to attach a click handler directly to a button:
$('#myBtn').click(function(){ alert('clicked'); });
This works when the element exists at the time the script runs, but falls apart when you add new buttons later with AJAX or append(). The new buttons never get the handler, and you end up re‑binding every time you add content—slow, memory‑hungry, and hard to maintain.
Event delegation with .on() solves that
Introduced in jQuery 1.7, .on() unifies direct binding and delegated binding. When you pass a selector as the second argument, jQuery attaches a single listener on the ancestor you call it on and checks the event target against the selector each time the event bubbles up.
Example: a list that grows with user input, where each item should log its text when clicked.
// Static parent
$('#myList').on('click', 'li', function(event){
console.log('Item clicked:', $(this).text());
});
// Dynamically added items
function addItem(text){
$('#myList').append('' + text + ' ');
}
addItem('First');
addItem('Second');
Notice the li selector in the .on() call. No matter how many li elements you add later, the click handler is already in place.
How delegation actually works
When the user clicks a li, the event bubbles to #myList. jQuery’s delegated handler runs once, checks if the original target matches the selector, and if so executes the callback. This means:
- Only one event listener lives on the ancestor, regardless of how many matching descendants exist.
- Memory usage stays low and you avoid leaks from orphaned handlers on removed nodes.
- Performance improves because the browser doesn’t have to maintain a large number of listeners.
Practical checklist before you switch
- Confirm jQuery 1.7+ – older projects using
.live()or.delegate()need an upgrade. - Choose the right ancestor – it should be static and as close to the dynamic elements as possible to keep the selector efficient.
- Test in DevTools – open the Events panel, trigger the event, and ensure only one listener appears on the ancestor.
- Verify no selector changes – if the structure of the dynamic content changes (e.g., new wrapper elements), update the selector accordingly.
Trade‑offs and limitations
While delegation is powerful, it’s not a silver bullet:
- Readability – a deeply nested selector can hide which elements are actually being targeted, making debugging harder.
- Selector specificity – overly broad selectors (e.g.,
.on('click', '*', …)) defeat the performance benefit. - IE8 and below – event bubbling behaves differently;
.on()works but may require polyfills for older browsers. - Dynamic removal – if you remove an element and then re‑add it with a different structure, the delegated event may not fire until you re‑bind.
Putting it all together in a real project
Suppose you’re building a comment box where users can add replies. Each reply is a div.reply that should collapse when clicked. Instead of binding a click to every reply on load, delegate from the container:
// Delegate collapse on reply click
$('#comments').on('click', '.reply', function(){
$(this).toggleClass('collapsed');
});
// Add a new reply dynamically
function addReply(content){
$('#comments').append('' + content + '');
}
Now the collapse behavior works for all current and future replies without re‑binding.
Actionable next steps
- Audit your codebase for
click(),hover(), and other direct bindings that could be delegated. - Replace them with
.on()using a static ancestor and a selector that matches the dynamic elements. - Run a quick DevTools test: add a new element, trigger the event, and confirm the handler fires and only one listener is present on the ancestor.
- Document the delegation strategy in your team's style guide to keep future contributors aware of the pattern.
Event delegation with .on() keeps your code lean, efficient, and future‑proof when your UI grows dynamically.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.