The Event Object
Every time an event fires, the browser hands your handler a single argument: the event object. It is a snapshot of what just happened — which element was involved, what type of event it was, where the pointer was, which key was pressed, and any data specific to that event. Reading these properties is how a generic handler decides what to actually do, so knowing the event object well is the key to writing flexible, reusable event code.
Receiving the event object
The browser always passes the event object as the first parameter to your listener. By convention it is named event, e, or evt. You never construct it yourself for native events — you just accept it and read from it.
button.addEventListener('click', (event) => {
console.log(event.type); // "click"
console.log(event.target); // the element that was clicked
console.log(event.timeStamp); // ms since the page loaded
});
The object you receive is an instance of a specialized subclass — MouseEvent for clicks, KeyboardEvent for key presses, InputEvent for typing, and so on. Each subclass adds properties relevant to its event, all built on the base Event interface.
Properties shared by every event
These live on the base Event interface, so they are available no matter what fired.
| Property | Description |
|---|---|
type | The event name as a string, e.g. "click", "keydown". |
target | The element that originally dispatched the event. |
currentTarget | The element whose listener is currently running. |
timeStamp | Milliseconds since page load when the event was created. |
bubbles | Whether the event travels up the DOM tree. |
defaultPrevented | true if preventDefault() has been called. |
isTrusted | true for real user actions, false for scripted dispatches. |
target vs currentTarget
This pair trips up almost everyone, and the distinction matters most when handling events on a parent. target is where the event started — the deepest element that was actually clicked. currentTarget is where the listener is attached — the element you called addEventListener on. During bubbling they can be different.
list.addEventListener('click', (e) => {
// e.currentTarget is always `list`
// e.target is the specific <li> (or child) you clicked
console.log(e.currentTarget === list); // true
console.log(e.target.tagName); // e.g. "LI"
});
Inside an arrow function,
thisis not the element. Usee.currentTargetto reliably reference the element the handler is bound to — it works in both arrow and regular functions.
Mouse coordinates
MouseEvent exposes several coordinate systems. They answer different questions, so pick the one that matches what you need.
| Property | Origin |
|---|---|
clientX / clientY | Top-left of the visible viewport. |
pageX / pageY | Top-left of the whole document (includes scroll). |
offsetX / offsetY | Top-left of the target element itself. |
screenX / screenY | Top-left of the physical screen. |
Use clientX/Y for fixed overlays, pageX/Y when scroll position counts, and offsetX/Y for drawing relative to an element such as a <canvas>.
Keyboard information
KeyboardEvent tells you what was pressed in two complementary ways. key is the produced character or named value ("a", "Enter", "ArrowUp"), affected by Shift and keyboard layout. code is the physical key ("KeyA", "Enter", "ArrowUp"), independent of layout — ideal for game controls like WASD.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
saveDocument();
}
});
Output:
// Pressing Ctrl+S logs nothing but blocks the browser's Save dialog
// Pressing Escape calls closeModal()
The modifier flags ctrlKey, shiftKey, altKey, and metaKey (Cmd on macOS, Win key on Windows) are booleans you combine with key to detect shortcuts.
Live demo: mouse and keyboard info
Move your mouse and press keys over the box below to watch the event object’s properties update in real time.
<div id="stage" tabindex="0"
style="font-family:system-ui;padding:24px;border:2px solid #6366f1;border-radius:12px;outline:none;cursor:crosshair;user-select:none">
<strong>Click here, move the mouse, then type.</strong>
<pre id="readout" style="margin-top:12px;color:#334155">waiting…</pre>
</div>
<script>
const stage = document.getElementById('stage');
const readout = document.getElementById('readout');
stage.addEventListener('mousemove', (e) => {
readout.textContent =
`type: ${e.type}\n` +
`client: ${e.clientX}, ${e.clientY}\n` +
`offset: ${e.offsetX}, ${e.offsetY}`;
});
stage.addEventListener('keydown', (e) => {
readout.textContent =
`type: ${e.type}\n` +
`key: ${e.key}\n` +
`code: ${e.code}\n` +
`modifiers: ${[e.ctrlKey && 'Ctrl', e.shiftKey && 'Shift', e.altKey && 'Alt']
.filter(Boolean).join('+') || 'none'}`;
e.preventDefault();
});
</script>
Event-specific data
Beyond the basics, each event type carries its own payload. Reading the right property saves you from querying the DOM separately.
inputevents exposee.target.value(the current text) ande.inputType(e.g."insertText","deleteContentBackward").wheelevents givee.deltaX/deltaY/deltaZfor scroll direction and amount.submitevents let you read the form viae.targetand prevent navigation withe.preventDefault().- Drag events carry a
e.dataTransferobject; clipboard events carrye.clipboardData.
<form id="signup" style="font-family:system-ui">
<input id="email" type="email" placeholder="[email protected]"
style="padding:8px;border:1px solid #cbd5e1;border-radius:6px" />
<p id="status" style="color:#475569">Type an email…</p>
</form>
<script>
const form = document.getElementById('signup');
const status = document.getElementById('status');
form.addEventListener('input', (e) => {
const valid = e.target.validity.valid && e.target.value.length > 0;
status.textContent = valid ? '✓ looks valid' : 'keep typing…';
status.style.color = valid ? '#16a34a' : '#475569';
});
form.addEventListener('submit', (e) => {
e.preventDefault(); // stop the page from reloading
status.textContent = `Submitted: ${form.email.value}`;
});
</script>
Best Practices
- Name the parameter
eventoreand read from it rather than reaching back into the DOM. - Use
e.currentTarget(notthis) for the bound element so your code works with arrow functions. - Reach for
e.targetwhen you need the exact element the user interacted with — essential for delegation. - Prefer
e.codefor physical key bindings (games, shortcuts) ande.keyfor text and named keys. - Pick the coordinate system deliberately:
offsetX/Yfor element-relative drawing,clientX/Yfor viewport overlays. - Check
e.isTrustedif a handler must only respond to genuine user input, not scripted dispatches.