Event Handling

This section documents jQuery 3.x (the current major line — no specific patch version is pinned). This content was generated with the assistance of AI and should be verified against the official jQuery API reference before being relied on in production, since API details and deprecations continue to change between releases.

jQuery is legacy-leaning: modern browsers implement native equivalents for almost everything it does (querySelectorAll, classList, fetch, addEventListener, append/prepend/remove, the Web Animations API), and most current stacks — Bootstrap 5 included — have dropped it. Prefer the native APIs for new work; this reference is aimed at reading, maintaining, or incrementally migrating code that already uses jQuery. Every page ends with a Modern equivalent note pointing at the native replacement.

This section’s bibliography lists the reference material consulted while preparing these pages.

.on() is the single method for attaching event handlers in modern jQuery. It registers a normalised, cross-browser event object and supports delegation. The older .bind(), .live(), and .delegate() are all removed or deprecated — everything they did, .on() does.

Binding handlers

$('#btn').on('click', handler);                 // direct binding
$('#btn').on('click', { id: 42 }, handler);     // pass data -> event.data.id
$('#btn').on('mouseenter mouseleave', handler); // several event types at once
$('#btn').one('click', handler);                // auto-removes itself after the first call

$('#btn').off('click', handler);   // remove one specific handler
$('#btn').off('click');            // remove all click handlers
$('#btn').off();                   // remove every handler on the element

Shorthand methods exist for the common events — .click(), .dblclick(), .hover(inFn, outFn), .focus(), .blur(), .change(), .submit(), .keydown(), .keyup(), .keypress(), and the mouse family (.mousedown(), .mouseup(), .mousemove(), .mouseenter(), .mouseleave()). Since jQuery 3.3 the no-argument signatures (.click() to trigger a click) are deprecated; call .trigger('click') instead. The one-argument forms (.click(handler)) still work but .on('click', handler) is preferred for consistency and because only .on() supports delegation.

The jQuery event object

The handler receives one argument: a jQuery-normalised event. this and event.currentTarget are the element the handler is bound to.

$('#form').on('submit', function (event) {
  event.type;              // "submit"
  event.target;            // the deepest element the event originated on
  event.currentTarget;     // === this: the element this handler is attached to
  event.which;             // normalised key/button code (legacy)
  event.key;               // "Enter", "a", ... (modern, from the native event)
  event.pageX;             // pointer X relative to the document
  event.data;              // the data object passed as .on('submit', data, fn)

  event.preventDefault();       // cancel the default action (no form submit)
  event.stopPropagation();      // stop the event bubbling to ancestors
  event.stopImmediatePropagation(); // also skip this element's remaining handlers
  return false;                 // shorthand for BOTH preventDefault + stopPropagation
});
In a jQuery handler return false is preventDefault() and stopPropagation(). In a native listener return false does nothing. This difference trips people up when porting code.

Event delegation

Bind one handler to a stable ancestor and pass a selector as the second argument. jQuery checks, at event time, whether event.target (or an ancestor of it, up to currentTarget) matches that selector:

// direct: only binds to the <li> that exist RIGHT NOW
$('#list li').on('click', handler);

// delegated: one handler on #list; works for <li> added later too
$('#list').on('click', 'li', function (event) {
  $(this).toggleClass('done');   // `this` is the matched <li>, not #list
});

Delegation matters for two reasons: dynamically added elements get the behaviour with no re-binding, and one listener uses less memory than hundreds. It is the standard pattern for lists, tables, and any repeated markup.

flowchart TB t["button#inner (event.target -- the click happens here)"] m["div#middle"] o["div#outer"] d["document (delegated handler bound here)"] t -->|"bubbles up"| m m -->|"bubbles up"| o o -->|"bubbles up"| d d --> chk["jQuery tests each node against the 'button' selector, runs the handler with this bound to the matched button element, and stopPropagation() here would halt the climb"]

Triggering and custom events

$('#btn').trigger('click');                 // fire click: handlers run AND default action
$('#btn').trigger('click', [arg1, arg2]);   // pass extra args to the handlers
$('#btn').triggerHandler('click');          // run handlers only: no default, no bubbling

$(document).on('cart:add', (e, item) => updateBadge(item));  // custom event
$(document).trigger('cart:add', { sku: 'A1' });              // fire it with data

$('#btn').on('click.plugin', handler);      // namespaced
$('#btn').off('.plugin');                   // remove every handler in the .plugin namespace

Namespaces let a plugin (or a component) remove exactly its own handlers without disturbing others.

Window and document events

$(window).on('resize', handler);
$(window).on('scroll', handler);       // consider throttling -- fires rapidly
$(window).on('load', handler);         // after all images/resources; rarely needed

Modern equivalent

el.addEventListener(type, handler, options) and removeEventListener. There is no built-in delegation — attach to a container and test inside: container.addEventListener('click', e ⇒ { const li = e.target.closest('li'); if (li) …​ }). Dispatch custom events with new CustomEvent('cart:add', { detail }) and el.dispatchEvent(…​). For the full capture/target/bubble model see Events.