JavaScript Events

Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.

TL;DR

  1. 01Attach event listeners with addEventListener for flexible handling.
  2. 02Use event delegation to handle many items with one listener.
  3. 03Control event flow with stopPropagation and preventDefault when needed.

Tips

  1. 01Use event delegation to attach a single listener to a container instead of many listeners on child elements.
  2. 02Pass the { once: true } option to addEventListener when a handler should only run a single time.
  3. 03Check event.cancelable before calling preventDefault, since some events like scroll cannot be canceled at all.

Warnings

  1. 01preventDefault only works on cancelable events — always check if the event is cancelable before calling it.
  2. 02Inline handlers like onclick can only hold one function, so a second assignment silently replaces the first one.
  3. 03Forgetting removeEventListener on elements you remove from the DOM can leak memory in long-running single-page applications.

Basic Event Listeners

    addEventListener()

    Attach event handlers for flexible, composable event handling.

    const button = document.querySelector("button");
    button.addEventListener("click", (event) => {
      console.log("Button clicked");
    });
    The event object

    Contains details about what happened, like type, target, and position.

    button.addEventListener("click", (e) => {
      console.log(e.type);      // "click"
      console.log(e.target);    // the button element
      console.log(e.clientX);   // mouse position
    });
    removeEventListener()

    Removes a listener when it's no longer needed.

    const handler = (e) => console.log("clicked");
    button.addEventListener("click", handler);
    button.removeEventListener("click", handler);
    Avoid inline handlers

    Inline event handlers like onclick are outdated compared to addEventListener.

    // Avoid: <button onclick="handleClick()">Click</button>
    // Use addEventListener instead
    { once: true }

    Fires a listener only one time, then removes it automatically.

    button.addEventListener("click", handler, { once: true });
    // Handler is automatically removed after first click

Event Bubbling and Capturing

    Bubbling by default

    Events bubble up from child to parent by default.

    div.addEventListener("click", () => console.log("div clicked"));
    button.addEventListener("click", () => console.log("button clicked"));
    // Clicking button logs both "button clicked" and "div clicked"
    stopPropagation()

    Prevents an event from bubbling up to parent listeners.

    button.addEventListener("click", (e) => {
      e.stopPropagation();
      console.log("button only");
    });
    Capture phase

    Pass true as the third argument to listen during the capture phase.

    div.addEventListener("click", handler, true);
    // Capture phase runs before bubble phase
    Most events bubble

    Most events bubble, but check the MDN docs for specific events that don't.

    Blocking parent handlers

    Use stopPropagation to prevent parent handlers from running at all.

Preventing Default Behavior

    preventDefault()

    Stops the browser's default action for an event.

    form.addEventListener("submit", (e) => {
      e.preventDefault();
      // Form does not submit to server
      console.log("Form submission intercepted");
    });
    Common use cases

    Works on clickable links, form submissions, and other cancelable events.

    link.addEventListener("click", (e) => {
      e.preventDefault();
      // Link does not navigate to href
    });
    event.defaultPrevented

    Check whether preventDefault was already called on the event.

    if (!event.defaultPrevented) {
      // Default action will occur
    }
    Not all events cancelable

    Not all events can be prevented — check if the event is cancelable first.

    event.cancelable

    Verify the event supports preventDefault before calling it.

    link.addEventListener("click", (e) => {
      if (e.cancelable) {
        e.preventDefault();
      }
    });

Event Delegation

    One listener, many children

    Attach a single listener to a parent to handle children efficiently.

    const list = document.querySelector("ul");
    list.addEventListener("click", (e) => {
      if (e.target.tagName === "LI") {
        console.log("Clicked item:", e.target.textContent);
      }
    });
    Fewer listeners

    This pattern is efficient when there are many similar elements.

    // Instead of attaching listeners to each item:
    items.forEach(item => item.addEventListener("click", handler));
    
    // Attach one listener to the container:
    container.addEventListener("click", handler);
    e.target.closest()

    Finds the closest matching ancestor from the actual clicked element.

    document.addEventListener("click", (e) => {
      const button = e.target.closest("button");
      if (button) console.log("Button clicked");
    });
    Works with dynamic elements

    Delegation works with elements added to the DOM after the listener was attached.

Common Events

    Mouse events

    click, dblclick, mousedown, mouseup, and mousemove track pointer activity.

    element.addEventListener("mousemove", (e) => {
      console.log(`Mouse at ${e.clientX}, ${e.clientY}`);
    });
    Keyboard events

    keydown and keyup track key presses; keypress is deprecated.

    document.addEventListener("keydown", (e) => {
      console.log(`Key pressed: ${e.key}`);
    });
    Form events

    change, input, submit, reset, focus, and blur track form interaction.

    input.addEventListener("input", (e) => {
      console.log(`Current value: ${e.target.value}`);
    });
    Window events

    load, unload, scroll, and resize track the page and viewport.

    window.addEventListener("scroll", () => {
      console.log("Page scrolled");
    });

In Practice

FAQ