JavaScriptIntermediate

JavaScript Event Object: target, currentTarget, Properties

Understand the event object: use event.target and currentTarget, read mouse, keyboard and form data, and build features like delegation.

All JavaScript lessons

What you will learn

When an event happens, the browser does not only run your function. It also hands you a package of information about what just happened: which element was clicked, where the mouse was, which key was pressed, what the user typed. That package is the event object. In this lesson you will learn how to receive it, the difference between event.target and event.currentTarget (a favourite interview question), the most useful properties for mouse, keyboard and form events, and how to use them in real features like event delegation and keyboard shortcuts.

What is the event object?

Imagine you order food, and the delivery person gives you not only the food but also a receipt: what was ordered, the time, who delivered it. The event object is that receipt.

The browser passes it automatically as the first argument of your handler. You only need to give it a name:

button.addEventListener("click", function (event) {
  console.log(event);
});

You can call the parameter event, e or evt. The name does not matter; the position does. If you do not need the information, you can leave the parameter out, as you did in the last lesson.

With arrow functions it looks the same:

button.addEventListener("click", (e) => console.log(e.type));

Different events give different kinds of objects

The event object is not always the same type. It has a basic part that every event has, plus extra parts for its kind:

Event Object type Extra information
click, mousemove, mousedown MouseEvent (click uses PointerEvent) Mouse position, buttons
keydown, keyup KeyboardEvent Which key, modifier keys
input InputEvent What was typed
submit SubmitEvent Which button submitted
focus, blur FocusEvent The other element involved
change, scroll Event Only the basics

All of them inherit from Event, so the basic properties work everywhere.

Tip: Run console.log(event) and expand the result in the Console to see everything available. It is the best way to explore.

The sample page

All examples use this page, also loaded in Practice in Editor:

<div id="outer" class="outer">
  Outer box
  <button id="inner" class="inner"><span class="icon">★</span> Like</button>
</div>

<ul id="tasks">
  <li data-id="1"><span class="text">Learn HTML</span>
    <button data-action="done">Done</button>
    <button data-action="delete">Delete</button></li>
  <li data-id="2"><span class="text">Learn CSS</span>
    <button data-action="done">Done</button>
    <button data-action="delete">Delete</button></li>
</ul>

<form id="settings">
  <input name="username" placeholder="Username">
  <input name="email" placeholder="Email">
  <label><input type="checkbox" name="news"> Newsletter</label>
</form>

<div id="pad" class="pad">Click anywhere in this pad</div>
<p id="out">-</p>

Basic properties (every event)

Property What it gives you
event.type The event name, such as "click"
event.target The element where the event started
event.currentTarget The element whose listener is running now
event.timeStamp Milliseconds since the page loaded
event.bubbles true if the event travels up to parent elements
event.cancelable true if preventDefault() can cancel it
event.defaultPrevented true if preventDefault() was already called
event.eventPhase 1 capturing, 2 at the target, 3 bubbling
event.isTrusted true if a real user caused it, false if code did (like el.click())
const inner = document.getElementById("inner");

inner.addEventListener("click", function (event) {
  console.log(event.type);        // "click"
  console.log(event.bubbles);     // true
  console.log(event.isTrusted);   // true when a real person clicked
});

event.target vs event.currentTarget

This pair confuses almost everyone at first, so we will go slowly.

  • event.target is the exact element that was clicked (the deepest one under the mouse).
  • event.currentTarget is the element that has the listener attached.

When a child is clicked, the event also bubbles up to its parents (the next-but-one lessons explain bubbling fully). So a listener on a parent can run for a click that happened on a child, and then the two are different.

An analogy: you ring the bell of flat 5, but the building’s security guard hears it and reacts. The target is flat 5’s bell (where it happened). The currentTarget is the guard (who is handling it now).

<div id="outer" class="outer">
  Outer box
  <button id="inner" class="inner"><span class="icon">★</span> Like</button>
</div>
const outer = document.getElementById("outer");

outer.addEventListener("click", function (event) {
  console.log("target:", event.target.tagName);
  console.log("currentTarget:", event.currentTarget.id);
});
You click on event.target event.currentTarget
The empty area of the outer box DIV (outer) outer
The text “Like” on the button BUTTON outer
The ★ icon inside the button SPAN outer

The currentTarget is always outer, because that is where the listener lives. The target changes with where you click.

currentTarget vs this

Inside a normal function, this is the same as currentTarget. Inside an arrow function, this is not the element (you learned this in the this lesson). event.currentTarget works in both, so it is the safer choice.

outer.addEventListener("click", function (event) {
  console.log(this === event.currentTarget);   // true
});

outer.addEventListener("click", (event) => {
  console.log(this === event.currentTarget);   // false: arrow functions do not set this
  console.log(event.currentTarget.id);         // "outer": always works
});

The “icon inside the button” problem

Suppose you want to know which button was clicked, but the user clicked the ★ icon inside it. Then event.target is the SPAN, not the button. Using event.target.id gives an empty string, and your code silently fails.

The fix is closest(), from the traversal lesson. It starts at the target and walks up until it finds a matching element:

outer.addEventListener("click", function (event) {
  const button = event.target.closest("button");

  if (button) {
    console.log("You clicked the button:", button.id);   // "inner" even if the ★ was clicked
  }
});

This pattern, event.target.closest(...), is the heart of event delegation, which is next.

Event delegation: one listener for many elements

Say you have a list of 100 tasks with Done and Delete buttons. Adding 200 listeners is wasteful, and items added later would have none. Instead, add one listener to the parent and use event.target to find out which child was clicked. This works because events bubble up.

const tasks = document.getElementById("tasks");

tasks.addEventListener("click", function (event) {
  const button = event.target.closest("button[data-action]");
  if (!button) return;                     // the click was not on one of our buttons

  const item = button.closest("li");

  if (button.dataset.action === "done") {
    item.classList.toggle("done");
  }

  if (button.dataset.action === "delete") {
    item.remove();
  }
});

Each button says what it does through a data-action attribute (from the attributes lesson). The one listener reads it and decides. New tasks added later work automatically, because the listener is on the parent, which never changes. The lessons on event propagation and delegation go deeper into this pattern.

Mouse event properties

Property Meaning
clientX, clientY Position relative to the visible browser window (viewport)
pageX, pageY Position relative to the whole page (includes the scrolled part)
offsetX, offsetY Position relative to the target element’s top-left corner
screenX, screenY Position relative to the monitor
button Which button: 0 left, 1 middle, 2 right
shiftKey, ctrlKey, altKey, metaKey true if that modifier key was held (metaKey is Cmd on Mac, Windows key on Windows)
detail Click count (1 for a single click, 2 for a double)

Which position should you use? If the user scrolls, clientY stays the same for a point on the screen, but pageY grows. For positioning something inside the clicked element (a dot, a ripple), use offsetX/Y. For positioning a floating tooltip with position: fixed, use clientX/Y.

const pad = document.getElementById("pad");

pad.addEventListener("click", function (event) {
  console.log("inside the pad:", event.offsetX, event.offsetY);
  console.log("in the window:", event.clientX, event.clientY);
  console.log("in the page:", event.pageX, event.pageY);
});

Careful: offsetX is relative to event.target. If the target is a child of the pad, offsetX is measured from the child, not the pad. For this reason, keep the pad free of children (or use getBoundingClientRect() and clientX).

Detect Shift + click

document.addEventListener("click", function (event) {
  if (event.shiftKey) {
    console.log("Shift was held: select a range");
  } else if (event.ctrlKey || event.metaKey) {
    console.log("Ctrl/Cmd was held: add to the selection");
  }
});

Keyboard event properties

Property Meaning Example
event.key The character or name the key produces "a", "A", "Enter", "Escape", "ArrowUp"
event.code The physical key on the keyboard "KeyA", "Enter", "Space", "ArrowUp"
event.ctrlKey, shiftKey, altKey, metaKey Modifier keys held true / false
event.repeat true if the key is being held and repeating

key vs code

key follows the meaning (it changes with Shift, with keyboard language and layout). code follows the position of the physical key (it never changes).

You press key code
The A key "a" "KeyA"
Shift + A "A" "KeyA"
The Space bar " " (a single space) "Space"
Left arrow "ArrowLeft" "ArrowLeft"
The key to the right of Tab, on a French keyboard "a" "KeyQ"

Rule of thumb: use event.key for shortcuts and typing (like "Enter" or "Escape"), and event.code for game controls like W, A, S, D, where the position of the key matters more than the letter.

document.addEventListener("keydown", function (event) {
  console.log("key:", event.key, "| code:", event.code, "| ctrl:", event.ctrlKey);

  if (event.repeat) return;   // ignore the repeats when a key is held down
});

Input and form event properties

For input and change, the most useful property is again event.target, which is the form control itself:

const email = document.querySelector("[name='email']");

email.addEventListener("input", function (event) {
  console.log(event.target.value);    // the text now in the box
  console.log(event.data);            // just the new character(s), like "a"
  console.log(event.inputType);       // "insertText", "deleteContentBackward", "insertFromPaste"...
});

What to read from event.target for each control:

Control Read Example value
Text, email, number, textarea event.target.value "riya@example.com"
Checkbox event.target.checked true / false
Radio group event.target.value (of the one changed) "pro"
<select> event.target.value "free"
File input event.target.files A list of File objects

One handler for many inputs

Because event.target tells you which input changed, a single listener on the <form> can handle all its fields. This works because input events bubble.

document.getElementById("settings").addEventListener("input", function (event) {
  const field = event.target;
  const value = field.type === "checkbox" ? field.checked : field.value;

  console.log(field.name + " = " + value);
});

Typing in the username field prints username = riya. Ticking the box prints news = true. The same code works if you add ten more fields, as long as each has a name.

The submit event

For submit, event.target (and currentTarget) is the form. Modern browsers also give event.submitter, the button that was used:

const form = document.getElementById("settings");

form.addEventListener("submit", function (event) {
  event.preventDefault();

  console.log(event.target === form);        // true
  console.log(event.submitter);              // the button that was clicked (null if Enter was used in some cases)

  console.log(form.elements.username.value); // read a field by its name
});

form.elements lets you reach each control by its name. The FormData lesson shows an even easier way to read a whole form.

Methods of the event object

You will learn these properly in the next lessons. Here is the short version:

Method What it does
event.preventDefault() Stops the browser’s default action: a link opening, a form submitting, a right-click menu
event.stopPropagation() Stops the event from travelling up to the parent elements
event.stopImmediatePropagation() Also stops other listeners on the same element
link.addEventListener("click", function (event) {
  event.preventDefault();   // the link will not open
});

Real use cases

1. Show the click position

A small tool that draws a dot where you click and shows the coordinates. Good for understanding offsetX, clientX and pageX.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Click Tracker</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    #pad { position: relative; width: 320px; height: 160px; background: #f1f5f9; border: 2px dashed #64748b; cursor: crosshair; }
    .dot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: tomato; pointer-events: none; }
  </style>
</head>
<body>
  <div id="pad"></div>
  <p id="info">Click inside the box.</p>

  <script>
    const pad = document.getElementById("pad");
    const info = document.getElementById("info");

    pad.addEventListener("click", function (event) {
      info.textContent =
        "offset: " + event.offsetX + ", " + event.offsetY +
        " | client: " + event.clientX + ", " + event.clientY +
        " | page: " + event.pageX + ", " + event.pageY;

      const dot = document.createElement("span");
      dot.className = "dot";
      dot.style.left = event.offsetX + "px";
      dot.style.top = event.offsetY + "px";
      pad.append(dot);
    });
  </script>
</body>
</html>

The pointer-events: none on the dot makes clicks pass through it, so offsetX is always measured from the pad.

2. Task list with delegation

The complete version of the delegation example. A single listener handles “Done”, “Delete”, and even tasks added later.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Delegation</title>
  <style>
    body { font-family: sans-serif; padding: 20px; max-width: 420px; }
    form { display: flex; gap: 8px; margin-bottom: 12px; }
    input { flex: 1; padding: 8px; font-size: 16px; }
    ul { list-style: none; padding: 0; }
    li { display: flex; gap: 6px; align-items: center; padding: 8px 0; border-bottom: 1px solid #e5e7eb; }
    li .text { flex: 1; }
    li.done .text { text-decoration: line-through; color: #6b7280; }
    button { cursor: pointer; padding: 4px 8px; }
  </style>
</head>
<body>
  <form id="form">
    <input id="task" type="text" placeholder="New task" required>
    <button type="submit">Add</button>
  </form>

  <ul id="tasks">
    <li><span class="text">Learn events</span> <button data-action="done">Done</button> <button data-action="delete">Delete</button></li>
  </ul>

  <script>
    const form = document.getElementById("form");
    const input = document.getElementById("task");
    const tasks = document.getElementById("tasks");

    // ONE listener for every present and future button
    tasks.addEventListener("click", function (event) {
      const button = event.target.closest("button[data-action]");
      if (!button) return;

      const item = button.closest("li");
      if (button.dataset.action === "done") item.classList.toggle("done");
      if (button.dataset.action === "delete") item.remove();
    });

    form.addEventListener("submit", function (event) {
      event.preventDefault();

      const li = document.createElement("li");
      li.innerHTML = '<span class="text"></span> <button data-action="done">Done</button> <button data-action="delete">Delete</button>';
      li.querySelector(".text").textContent = input.value;   // user text via textContent
      tasks.append(li);

      input.value = "";
    });
  </script>
</body>
</html>

Compare this with the to-do list in the last lesson, where every new Delete button needed its own listener. Here, new tasks need nothing extra.

3. Keyboard shortcuts with modifier keys

The event object tells you if Ctrl, Shift or Alt were held, which lets you build real shortcuts. Try Ctrl + B (bold toggle), Ctrl + Shift + D (dark toggle) or Esc.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Shortcuts</title>
  <style>
    body { font-family: sans-serif; padding: 20px; transition: background 0.2s, color 0.2s; }
    body.dark { background: #111827; color: #f9fafb; }
    #text.bold { font-weight: bold; }
    kbd { background: #e5e7eb; color: #111827; padding: 2px 6px; border-radius: 4px; }
  </style>
</head>
<body>
  <p><kbd>Ctrl</kbd> + <kbd>B</kbd> bold &nbsp; <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> dark mode &nbsp; <kbd>Esc</kbd> reset</p>
  <p id="text">Press a shortcut and watch this text.</p>
  <p id="last">Last key: -</p>

  <script>
    const text = document.getElementById("text");
    const last = document.getElementById("last");

    document.addEventListener("keydown", function (event) {
      last.textContent = "Last key: " + event.key + " (code: " + event.code + ")";

      const ctrl = event.ctrlKey || event.metaKey;   // Ctrl on Windows, Cmd on Mac
      const key = event.key.toLowerCase();

      if (ctrl && !event.shiftKey && key === "b") {
        event.preventDefault();
        text.classList.toggle("bold");
      }

      if (ctrl && event.shiftKey && key === "d") {
        event.preventDefault();
        document.body.classList.toggle("dark");
      }

      if (event.key === "Escape") {
        text.classList.remove("bold");
        document.body.classList.remove("dark");
      }
    });
  </script>
</body>
</html>

4. Custom right-click menu

contextmenu is the right-click event. We cancel the browser’s own menu and place ours at the click position.

const menu = document.getElementById("menu");   // a hidden, position: fixed list

document.addEventListener("contextmenu", function (event) {
  event.preventDefault();                       // no browser menu

  menu.style.left = event.clientX + "px";
  menu.style.top = event.clientY + "px";
  menu.hidden = false;
});

document.addEventListener("click", function () {
  menu.hidden = true;                           // any click closes it
});

Since the menu is position: fixed, clientX/Y (viewport coordinates) are the right choice.

5. Live form summary with one listener

Show a preview of the whole form while the user types, with one input listener on the form:

const form = document.getElementById("settings");
const preview = document.getElementById("preview");

form.addEventListener("input", function () {
  const data = {};

  for (const field of form.elements) {
    if (!field.name) continue;
    data[field.name] = field.type === "checkbox" ? field.checked : field.value;
  }

  preview.textContent = JSON.stringify(data, null, 2);
});

Common mistakes

  • Confusing target and currentTarget. target is where it happened, and currentTarget is where the listener is. Use currentTarget for “the element I attached this to”.
  • Reading event.target.id when the click hit a child. Clicking the ★ inside a button gives SPAN. Use event.target.closest("button").
  • Using this in an arrow function. It is not the element. Use event.currentTarget.
  • Forgetting the parameter. Writing function () { console.log(event.target) } may work by accident in some browsers (through the old window.event), but it is unreliable. Always declare function (event).
  • Using event.target.value on a non-input. A div or button has no useful value. Check the type first.
  • Mixing up clientX, pageX and offsetX. offset is inside the element, client is inside the window, page is inside the whole page.
  • Using keyCode or which. They are deprecated. Use event.key and event.code.
  • Comparing event.key to the wrong case. Shift changes "b" into "B". Use event.key.toLowerCase() when case does not matter.
  • Ignoring metaKey for shortcuts. On a Mac, the shortcut key is Cmd, not Ctrl.
  • Not checking event.repeat. Holding a key fires keydown many times. Ignore the repeats when you want one action per press.
  • Keeping the event object for later. The event object is for use inside the handler. Copy the values you need (const x = event.clientX) if you use them later.
  • Forgetting if (!button) return; in delegation. A click on empty space then gives null, and null.dataset crashes.

Practice

  1. Log event.type, event.target and event.currentTarget for a click on a button. Then log the same for a button that has an icon inside, and click the icon.
  2. Put a listener on a parent div with three child buttons. Print the text of the button that was clicked using event.target.textContent.
  3. Show event.key and event.code for the letter A, Shift + A, the space bar, and the arrow keys.
  4. Make a mousemove listener that shows clientX and clientY live in a paragraph. Scroll the page and see which of clientY and pageY changes.
  5. Click in a box and draw a dot where you clicked, using offsetX and offsetY.
  6. Use one input listener on a form with three fields. Print the field name and value for each change.
  7. Build the delegation task list from this lesson, and add a third button, “Edit”, that changes the task text with prompt().
  8. Make Ctrl + S show an alert instead of saving the page. What must you call to stop the browser’s own save dialog?
  9. Add a counter that ignores keydown repeats, so holding a key counts only once.
  10. Challenge: build a small image-gallery menu. A parent div holds 5 thumbnail img elements with data-full attributes. With one click listener, show the full image name in a heading when a thumbnail is clicked (use closest("img")).

Recap

  • The event object is passed to your handler as the first argument. It contains information about what happened. Name it event or e.
  • Every event has type, target, currentTarget, timeStamp, bubbles, cancelable and isTrusted. Mouse, keyboard, input and submit events add their own properties.
  • event.target is the element where the event started. event.currentTarget is the element that owns the running listener (and it works in arrow functions, where this does not).
  • When a child can be clicked, use event.target.closest("selector") to find the element you really want.
  • Event delegation puts one listener on a parent and uses event.target to find the child. It saves code and works for elements added later.
  • Mouse: clientX/Y (window), pageX/Y (page), offsetX/Y (element), button and the modifier keys.
  • Keyboard: use event.key for the character or name and event.code for the physical key. Check ctrlKey, shiftKey, altKey, metaKey and repeat.
  • Forms: read event.target.value or .checked, and use event.target.name to handle many inputs with one listener.
  • Do not use the deprecated keyCode, which or keypress.
  • Next you will learn addEventListener in depth, including removing listeners and options like once, followed by event propagation and preventDefault().