React to Clicks, Keys, and Everything In Between
JavaScript DOM Events
`addEventListener` wires functions to user actions. Learn the event object, default actions, bubbling, and delegation.
What you'll learn
- Wire up event listeners with `addEventListener`
- Use the event object and prevent defaults
- Use event delegation for dynamic lists
The DOM is event-driven. Clicks, key presses, form submits, scrolls, network responses — all of them are events you can listen for.
The Standard Listener
const btn = document.querySelector("button");
btn.addEventListener("click", () => {
console.log("clicked!");
}); The first argument is the event name (no "on" prefix); the second
is the function to run. You can attach multiple listeners to the
same element — they all fire.
The Event Object
The callback gets an event object with details about what happened.
document.addEventListener("click", e => {
console.log(e.type); // "click"
console.log(e.target); // the element that was clicked
console.log(e.x, e.y); // viewport coordinates
}); Common properties:
| Property | What it tells you |
|---|---|
e.type | The event name ("click", "keydown", etc.) |
e.target | The element the event happened on |
e.currentTarget | The element the listener is attached to |
e.key | On keyboard events: the key ("Enter", "a") |
e.preventDefault() | Cancel the browser’s default behavior |
e.stopPropagation() | Stop the event from bubbling further |
Preventing Defaults
Some events have built-in browser behavior — submitting a form
navigates, clicking a link follows it. preventDefault() cancels
that so you can handle it yourself.
const form = document.querySelector("form");
form.addEventListener("submit", e => {
e.preventDefault(); // don't actually submit
console.log("would submit here");
}); Bubbling
Events bubble up the tree: a click on a <span> inside a <button>
inside a <div> fires on the span first, then the button, then the
div — all the way up to document. Most events bubble.
Delegation — One Listener for Many
Instead of attaching a listener to every list item, attach one
to the parent and check e.target.
const list = document.querySelector(".todo");
list.addEventListener("click", e => {
if (e.target.matches(".todo__delete")) {
e.target.closest("li").remove();
}
}); Why it matters: when you add or remove items later, the listener
still works — there’s nothing to re-wire. el.matches(selector)
asks “does this element match the selector?” and
el.closest(selector) walks up until something does.
Removing Listeners
To remove a listener, you need the same function reference.
function onClick() { console.log("hi"); }
const btn = document.querySelector("button");
btn.addEventListener("click", onClick);
btn.removeEventListener("click", onClick); // gone Anonymous arrow functions can’t be removed because you have no handle to them.
once and Other Options
The third argument to addEventListener accepts an options object.
document.addEventListener("click", () => console.log("one shot"), {
once: true, // remove after firing once
}); Other useful flags: { capture: true } (fire on the way DOWN
instead of bubbling up), { passive: true } (promise not to call
preventDefault — improves scroll performance).
Up Next
Forms get their own little world of events and APIs.
JavaScript DOM Forms →