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.
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.targetis the exact element that was clicked (the deepest one under the mouse).event.currentTargetis 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:
offsetXis relative toevent.target. If the target is a child of the pad,offsetXis measured from the child, not the pad. For this reason, keep the pad free of children (or usegetBoundingClientRect()andclientX).
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 <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> dark mode <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
targetandcurrentTarget.targetis where it happened, andcurrentTargetis where the listener is. UsecurrentTargetfor “the element I attached this to”. - Reading
event.target.idwhen the click hit a child. Clicking the ★ inside a button givesSPAN. Useevent.target.closest("button"). - Using
thisin an arrow function. It is not the element. Useevent.currentTarget. - Forgetting the parameter. Writing
function () { console.log(event.target) }may work by accident in some browsers (through the oldwindow.event), but it is unreliable. Always declarefunction (event). - Using
event.target.valueon a non-input. Adivorbuttonhas no usefulvalue. Check the type first. - Mixing up
clientX,pageXandoffsetX.offsetis inside the element,clientis inside the window,pageis inside the whole page. - Using
keyCodeorwhich. They are deprecated. Useevent.keyandevent.code. - Comparing
event.keyto the wrong case. Shift changes"b"into"B". Useevent.key.toLowerCase()when case does not matter. - Ignoring
metaKeyfor shortcuts. On a Mac, the shortcut key is Cmd, not Ctrl. - Not checking
event.repeat. Holding a key fireskeydownmany 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 givesnull, andnull.datasetcrashes.
Practice
- Log
event.type,event.targetandevent.currentTargetfor a click on a button. Then log the same for a button that has an icon inside, and click the icon. - Put a listener on a parent
divwith three child buttons. Print the text of the button that was clicked usingevent.target.textContent. - Show
event.keyandevent.codefor the letterA,Shift + A, the space bar, and the arrow keys. - Make a
mousemovelistener that showsclientXandclientYlive in a paragraph. Scroll the page and see which ofclientYandpageYchanges. - Click in a box and draw a dot where you clicked, using
offsetXandoffsetY. - Use one
inputlistener on a form with three fields. Print the field name and value for each change. - Build the delegation task list from this lesson, and add a third button, “Edit”, that changes the task text with
prompt(). - Make
Ctrl + Sshow an alert instead of saving the page. What must you call to stop the browser’s own save dialog? - Add a counter that ignores
keydownrepeats, so holding a key counts only once. - Challenge: build a small image-gallery menu. A parent
divholds 5 thumbnailimgelements withdata-fullattributes. With one click listener, show the full image name in a heading when a thumbnail is clicked (useclosest("img")).
Recap
- The event object is passed to your handler as the first argument. It contains information about what happened. Name it
eventore. - Every event has
type,target,currentTarget,timeStamp,bubbles,cancelableandisTrusted. Mouse, keyboard, input and submit events add their own properties. event.targetis the element where the event started.event.currentTargetis the element that owns the running listener (and it works in arrow functions, wherethisdoes 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.targetto find the child. It saves code and works for elements added later. - Mouse:
clientX/Y(window),pageX/Y(page),offsetX/Y(element),buttonand the modifier keys. - Keyboard: use
event.keyfor the character or name andevent.codefor the physical key. CheckctrlKey,shiftKey,altKey,metaKeyandrepeat. - Forms: read
event.target.valueor.checked, and useevent.target.nameto handle many inputs with one listener. - Do not use the deprecated
keyCode,whichorkeypress. - Next you will learn
addEventListenerin depth, including removing listeners and options likeonce, followed by event propagation andpreventDefault().