cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Introduction to events

Learn how addEventListener connects JavaScript to clicks, keys, input, and other browser events, how to read event objects, and how to remove listeners safely.

By the end, you can
  • 01
    Install listenersUse addEventListener with a type and a callback.
  • 02
    Read event detailsUse target, currentTarget, timeStamp, isTrusted, and type-specific fields.
  • 03
    Clean upRemove listeners and use once, passive, and signal options correctly.

What events are

A browser page is not just a picture. People click buttons, press keys, type text, move pointers, focus fields, scroll, resize the window, and sometimes your own code creates events too. An event is the browser’s announcement that one of those things happened.

A listener is a function you register for a specific event type. When that event is dispatched to the element, the browser calls your function and gives it an event object with details about the event. If you learned callbacks, this is the callback idea attached to the page.

Tiny definition

Events are browser notifications. addEventListener says, “when this kind of notification happens on this element, call this function.”

Real-life analogyA listener is a doorbell

Imagine a door with no bell. Visitors may arrive, but the house does not know. You install a bell for one kind of visit: “when someone presses this button, ring inside.” That is addEventListener: pick a page element, pick an event type, and give the browser a function to call.

In real life: The door
In JavaScript: A DOM element, like a button
In real life: The kind of visit you care about
In JavaScript: An event type, like click
In real life: Installing the bell
In JavaScript: Calling addEventListener
In real life: The house responds when it rings
In JavaScript: The browser calls your listener function

Where the analogy stops: A real doorbell rings for anyone at the door. DOM events have precise rules about targets, phases, default actions, and trust. This lesson starts with the listener; the next lesson goes deeper into travel through the page.

In this lesson you will inspect real event objects, fall into and fix the classic removeEventListener trap, try the once, passive, and signal options, and step through the exact order of listener registration and dispatch.

addEventListener: type, function, options

CORE API

The method shape is element.addEventListener(type, listener, options). The type is a string such as "click", "keydown", or "input". The listener is a function (or a handler object) that the browser will call later. The third argument is optional: it can be an options object or the older boolean useCapture.

A small click listenerPop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#save");const status = document.querySelector("#status"); button.addEventListener("click", (event) => {  status.textContent = "Saved at " + Math.round(event.timeStamp) + "ms";});

The important part: line 4 does not run the arrow function immediately. It stores it. Later, when the click happens, the browser calls it synchronously during event dispatch. If the listener changes text, classes, or state, that change happens before the browser returns from dispatching that event.

The three arguments to addEventListener
PartExampleMeaning
type"click"The name of the event to listen for.
listenerhandleClickThe function or handler object to call later.
options{ once: true }Extra rules such as one-time use, passive behavior, signal cleanup, or capture.

You can add more than one listener to the same element and event type. They are independent unless they are the exact same registration: same event type, same listener object, and same capture value. Duplicate registrations with those three pieces equal are ignored.

The event object: the delivery slip

INTERACTIVE

Every listener receives an event object. It tells you what happened, where it happened, when it happened, whether it came from a real user action, and extra details for that kind of event. A key event has key and code. A mouse or pointer event has coordinates and a button.

Real-life analogyThe event object is a delivery slip

When a package arrives, the slip says what it is, where it came from, and who signed for it. The event object is the browser’s slip. You usually do not need every field, but knowing where to look keeps listener code small.

In real life: Package type
In JavaScript: event.type, such as click
In real life: Delivery address
In JavaScript: event.target, where it happened
In real life: Desk currently processing it
In JavaScript: event.currentTarget, whose listener is running
In real life: Timestamp and signature
In JavaScript: timeStamp and isTrusted

Where the analogy stops: A delivery slip is just paper. An event object is live during dispatch and also has methods such as preventDefault(). Some details depend on the event type.

Try the inspector. Turn event types on and off, then interact with the live controls. The fake click button dispatches new MouseEvent("click"), so the log shows isTrusted=false. Real user actions show isTrusted=true in the browser.

Event inspector
Event inspector sourcePop out in the code editor (opens in a new tab)JavaScript
const controls = document.querySelector("#inspector");const log = document.querySelector("#event-log"); function inspect(event) {  const details = {    type: event.type,    target: event.target.id,    currentTarget: event.currentTarget.id,    timeStamp: Math.round(event.timeStamp),    isTrusted: event.isTrusted,    key: event.key,    code: event.code,    button: event.button,    clientX: event.clientX,    clientY: event.clientY,  };  log.prepend(JSON.stringify(details));} controls.addEventListener("click", inspect);controls.addEventListener("keydown", inspect);controls.addEventListener("input", inspect);
Live DOM controls3 types
  1. No events yet.
Try it yourself

Choose event types, then click, type, focus, or blur the controls.

The demo nodes are created and wired with DOM APIs inside an effect, then every listener is removed during cleanup. A fake click is created by script, so isTrusted is false.

Two fields are easy to mix up. event.target is where the event started. event.currentTarget is the element whose listener is currently running. In this lesson most listeners sit directly on the control they read; the next lesson, Bubbling & capturing, explores why these can differ.

removeEventListener needs the same key

INTERACTIVE

Removing is exact: removeEventListener(type, listener, options) looks for a listener with the same event type, the same listener object, and the same capture value. Other options such as once and passive are not used for matching, but capture is.

Real-life analogyReturn the exact key

If a building gives you a key, returning a different key that looks the same does not unlock the record. Inline arrows create a fresh function each time, so the browser cannot match the listener you meant to remove.

In real life: The original key
In JavaScript: The same function reference
In real life: A copy cut to look similar
In JavaScript: A new inline arrow
In real life: Front-door vs back-door lock
In JavaScript: The capture value must match

Where the analogy stops: Browsers are not judging what the function source text looks like. They compare object identity and capture, not whether two functions would do the same work.

removeEventListener trap
The removal trapPop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("#alarm");let rings = 0; button.addEventListener("click", () => rings++);button.removeEventListener("click", () => rings++);button.click(); // rings is 1: the first arrow is still installed function ring() { rings++; }button.addEventListener("click", ring, { capture: true });button.removeEventListener("click", ring, { capture: false });button.click(); // rings is 3: capture did not matchbutton.removeEventListener("click", ring, { capture: true });
Alarm0 rings
    Try it yourself

    Rings: 0. Removal matches by event type, listener object, and capture value.

    This lab intentionally shows the bug first. Reset replaces the DOM button so old listeners are gone.
    Will this listener be removed?
    • addEventListener("click", save); removeEventListener("click", save);
    • addEventListener("click", () => save()); removeEventListener("click", () => save());
    • addEventListener("click", save, true); removeEventListener("click", save, true);
    • addEventListener("click", save, true); removeEventListener("click", save, false);
    • A { once: true } listener after its first event
    • addEventListener("click", logger); removeEventListener("click", logger); where logger.handleEvent exists
    Try it yourself
    0 of 6 correct

    Sort each case by whether the listener is really removed.

    Choose a category for every card. You can change an answer at any time; Reset clears them all.

    once, passive & signal options

    INTERACTIVE

    Listener options make cleanup and performance clearer. { once: true } removes a listener after the first call. { passive: true } promises that the listener will not cancel the browser’s default action. { signal } connects the listener to an AbortController so aborting the signal removes it. The Cancellation with AbortController lesson covers the same signal idea for asynchronous work.

    Real-life analogyOptions are rules on the bell

    Sometimes the bell should work once. Sometimes it should only observe traffic and never block it. Sometimes a building manager needs one switch that turns off several bells at once. Those are once, passive, and signal.

    In real life: A one-time voucher
    In JavaScript: once: true
    In real life: A promise not to block the road
    In JavaScript: passive: true
    In real life: One master switch for many bells
    In JavaScript: signal: controller.signal

    Where the analogy stops: Options do not change what a click or key event means. They change how the browser manages the listener around that event.

    Options lab
    Listener optionsPop out in the code editor (opens in a new tab)JavaScript
    const controller = new AbortController(); button.addEventListener("click", celebrate, { once: true });first.addEventListener("click", log, { signal: controller.signal });second.addEventListener("pointerdown", log, { signal: controller.signal });third.addEventListener("keydown", log, { signal: controller.signal }); checkbox.addEventListener("click", (event) => {  event.preventDefault();  console.log(event.defaultPrevented);}, { passive: true }); controller.abort();
    Live options0 calls

    once: 0 · signal: 0 · passive: 0

    Try it yourself

    Try once, signal, and passive in the real browser.

    Chrome verified here: once removes after one call, AbortController removes all signaled listeners, and passive click cancellation is ignored with defaultPrevented remaining false. Some browsers also default certain wheel and touch listeners on the page to passive.
    Which option would you use?
    • Close a welcome popover after the first outside click
    • Listen to scrolling or touch movement without blocking smooth scrolling
    • Remove several listeners when a component unmounts
    • Capture the first key a learner presses in a shortcut trainer
    • Cancel pointermove, pointerup, and keydown listeners together
    • Measure wheel events without ever cancelling them
    Try it yourself
    0 of 6 correct

    Match each situation to the most helpful listener option.

    Choose a category for every card. You can change an answer at any time; Reset clears them all.

    In passive listeners, preventDefault() is ignored; browsers may log a warning. In some browsers, certain wheel, touchstart, and touchmove listeners on window, document, or body default to passive for scrolling performance, so state your intent with an options object when cancellation matters.

    Registration and dispatch order

    STEP THROUGH

    Registration and dispatch are different moments. Registration stores a listener for later. Dispatch is the event happening now. During dispatch, listeners on the same element run synchronously in registration order. Adding the same function twice with the same type and capture value registers it once, not twice.

    Registration and dispatch order
    Step 0 of 8Ready
    Your turn: follow the blue line

    Step through registration first, then dispatch. Predict which listeners run and in what order.

    Running in
    1. script
    Next: line 1
    Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
    const button = document.querySelector("#save");function first(event) { log.push("first:" + (this === event.currentTarget)); }const second = (event) => log.push("second:" + (this === event.currentTarget)); button.addEventListener("click", first);button.addEventListener("click", second);button.addEventListener("click", first); // ignored: same type + function + capturebutton.dispatchEvent(new Event("click"));console.log(log.join(" | "));
    CallStoreChangeResultRun = next line. Ran = already executed.
    Recent returnsNothing yet. Start with the blue line.
    Change line 8
    A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

    Notice the this detail. A regular function listener receives this as event.currentTarget. An arrow listener keeps the outer this, so it is usually safer to read event.currentTarget. The this keyword lesson explains that binding rule in depth.

    onclick, addEventListener, and handler objects

    COMPARE

    You will still see onclick. As a DOM property, it holds one handler. Assigning another function overwrites the previous one. addEventListener allows many independent listeners, which is why modern code uses it for most behavior. HTML attributes such as <button onclick="save()"> are discouraged because they mix markup and JavaScript and are harder to compose safely.

    Property handlers vs addEventListener
    Handler formsPop out in the code editor (opens in a new tab)JavaScript
    const button = document.querySelector("button"); button.onclick = () => console.log("first property handler");button.onclick = () => console.log("second property handler"); button.addEventListener("click", () => console.log("listener A"));button.addEventListener("click", () => console.log("listener B")); const logger = {  handleEvent(event) {    console.log("object saw " + event.type);  },};button.addEventListener("click", logger);
    Live button0 calls
      Try it yourself
      Click the live button, then reset the log.

      The onclick property holds one handler, so the second assignment wins. addEventListener keeps adding independent listeners.

      HTML attributes such as onclick='...' are older and mix markup with code, so modern lessons avoid them.
      Three ways to attach event code
      FormHow many?Use it?
      element.onclick = fnOne property valueUseful to recognize; easy to overwrite.
      element.addEventListener(type, fn)Many listenersPreferred for application code.
      element.addEventListener(type, object)Many handler objectsGreat when an object should handle several event types with handleEvent.
      onclick="..." in HTMLOne inline attributeAvoid in modern code; keep behavior in JavaScript.

      Where you will use events

      Events are the front-end glue for real interfaces. A menu opens on click. A search field updates suggestions on input. A keyboard shortcut listens for keydown. A drag tool listens for pointerdown, pointermove, and pointerup. A cleanup function removes listeners when a widget disappears.

      A practical setup with cleanupPop out in the code editor (opens in a new tab)JavaScript
      function mountSearch(input, results) {  const controller = new AbortController();  const { signal } = controller;   input.addEventListener("input", updateResults, { signal });  input.addEventListener("keydown", closeOnEscape, { signal });   function updateResults(event) {    results.textContent = "Searching for " + event.currentTarget.value;  }   function closeOnEscape(event) {    if (event.key === "Escape") results.textContent = "";  }   return () => controller.abort();}

      This pattern is especially useful next to UI libraries. Let the framework render its own elements, but attach real DOM listeners in an effect or setup step, and remove them in cleanup. If you create DOM nodes by hand, keep them inside a container the framework does not manage.

      Common misconceptions

      • “addEventListener runs my function now.” It registers the function. Dispatch calls it later.
      • “Two identical arrows are the same listener.” They are two different function objects.
      • “target and currentTarget are always equal.” They match in simple cases, but differ when an event starts inside another element’s listener.
      • “passive just makes code faster.” Passive is a promise not to cancel default behavior; speed comes from letting the browser proceed.
      • “dispatchEvent creates a real user event.” Script-created events are useful for code paths, but isTrusted is false.
      • “onclick is the same as addEventListener.” The property holds one handler; addEventListener allows many.
      Similar event ideas that are easy to confuse
      IdeaMeansRemember
      targetWhere the event beganThe original delivery address.
      currentTargetWhose listener is running nowThe desk currently processing the slip.
      onceRemove after first callOne-time voucher.
      signalRemove when abortedMaster switch.
      passiveDo not block default behaviorPromise not to block the road.

      Practice exercises

      5 EXERCISES
      Exercise 1 · Warm-upPredict the duplicate listener

      Assume button is a real DOM button. What does the final line print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const calls = [];
      function save() { calls.push("save"); }
      button.addEventListener("click", save);
      button.addEventListener("click", save);
      button.dispatchEvent(new Event("click"));
      console.log(calls.length);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 2 · PracticeFix an inline removal bug

        What concept fixes the bug? Type the missing word: keep a function ____.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        button.addEventListener("click", () => save());
        button.removeEventListener("click", () => save());

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 3 · PracticeChoose cleanup with a signal

          Write a setup pattern for three listeners that can be removed together.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // Add click, input, and keydown listeners.
          // Remove all of them later with one call.
            Exercise 4 · Warm-upProperty handler overwrite

            What text prints?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const calls = [];
            const button = {};
            button.onclick = () => calls.push("first");
            button.onclick = () => calls.push("second");
            button.onclick();
            console.log(calls.join(", "));

            Answer, then press Check. Spacing and letter case don’t matter.

              Exercise 5 · ChallengeBuild a tiny inspector

              Complete the listener in your browser console or editor.

              Starter codePop out in the code editor (opens in a new tab)JavaScript
              const box = document.querySelector("#box");
              box.addEventListener("pointerdown", (event) => {
                // Fill in: log type, currentTarget id, and coordinates.
              });

                Check your understanding

                7 QUESTIONS
                Introduction to events quiz · 7 questionsScore: first tries count
                1. Question 1 of 7What does addEventListener("click", save) do?

                  Choose an answer to see the explanation.

                2. Question 2 of 7Which property is the element whose listener is currently running?

                  Choose an answer to see the explanation.

                3. Question 3 of 7What does this print?

                  Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                  const calls = [];
                  function save() { calls.push("save"); }
                  button.addEventListener("click", save);
                  button.addEventListener("click", save);
                  button.dispatchEvent(new Event("click"));
                  console.log(calls.length);

                  Choose an answer to see the explanation.

                4. Question 4 of 7Why does this removal fail?

                  Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                  button.addEventListener("click", () => save());
                  button.removeEventListener("click", () => save());

                  Choose an answer to see the explanation.

                5. Question 5 of 7Which option removes a listener automatically after its first call?

                  Choose an answer to see the explanation.

                6. Question 6 of 7What is true about a passive listener that calls preventDefault()?

                  Choose an answer to see the explanation.

                7. Question 7 of 7What does isTrusted become for button.dispatchEvent(new Event("click"))?

                  Choose an answer to see the explanation.

                Key takeaways

                • addEventListener(type, listener, options) registers code for a future event.
                • The event object tells you the type, target, currentTarget, timing, trust, and type-specific details.
                • removeEventListener needs the same listener object and the same capture value.
                • once removes after one call, passive refuses cancellation, and signal removes on abort.
                • Property handlers hold one function; addEventListener composes many listeners.

                An event listener is a function the browser calls when a named event is dispatched to an element.

                Up next: Bubbling & capturing.

                CompleteFrontend Clear concepts. Working examples.