Events
Learn how to listen and respond to user interactions using JavaScript events.
Events
Everything you do on a webpage is an event. Clicking a button — event. Typing in an input — event. Scrolling the page — event. Moving the mouse — event. Submitting a form — event.
An event is a signal that something happened. JavaScript lets you listen for these signals and run code in response. This is what makes pages interactive.
const btn = document.querySelector("button");
btn.addEventListener("click", () => {
console.log("Button was clicked!");
});Nothing runs until the user clicks. Then the function fires. That function is called an event handler or event listener.
addEventListener()
The standard way to listen for events in modern JavaScript.
element.addEventListener(eventType, handler);eventType— a string describing the event —"click","keydown","submit"etc.handler— the function to run when the event fires
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", function() {
console.log("Clicked with regular function");
});
// Arrow function — more common in modern code
btn.addEventListener("click", () => {
console.log("Clicked with arrow function");
});Adding multiple listeners
You can add as many listeners as you want to the same element — they all fire:
btn.addEventListener("click", () => console.log("First handler"));
btn.addEventListener("click", () => console.log("Second handler"));
// Both run when clickedRemoving a listener
To remove a listener, pass the same function reference to removeEventListener. This is why named functions matter here — anonymous arrow functions cannot be removed.
function handleClick() {
console.log("Clicked!");
}
btn.addEventListener("click", handleClick);
// Later — remove it
btn.removeEventListener("click", handleClick);The Event Object
Every event handler automatically receives an event object as its first argument. It contains everything about what just happened — what was clicked, what key was pressed, where the mouse was.
btn.addEventListener("click", (event) => {
console.log(event); // the full event object
console.log(event.type); // "click"
console.log(event.target); // the element that was clicked
console.log(event.timeStamp); // when it happened
});event is just a convention — you can name it anything. e and evt are also common:
btn.addEventListener("click", (e) => {
console.log(e.target); // the button element
});event.target vs event.currentTarget
This is important. target is the element that actually triggered the event. currentTarget is the element the listener is attached to. They can be different — more on this in the Event Delegation topic.
btn.addEventListener("click", (e) => {
console.log(e.target); // what was actually clicked
console.log(e.currentTarget); // what has the listener — the button
});Common Event Types
Mouse Events
const box = document.querySelector(".box");
box.addEventListener("click", () => console.log("Clicked"));
box.addEventListener("dblclick", () => console.log("Double clicked"));
box.addEventListener("mouseenter", () => console.log("Mouse entered"));
box.addEventListener("mouseleave", () => console.log("Mouse left"));
box.addEventListener("mousemove", (e) => {
console.log(`Mouse at: ${e.clientX}, ${e.clientY}`);
});clientX and clientY give the mouse position relative to the viewport.
Keyboard Events
document.addEventListener("keydown", (e) => {
console.log(e.key); // "a", "Enter", "ArrowUp" etc.
console.log(e.code); // "KeyA", "Enter", "ArrowUp" etc.
console.log(e.ctrlKey); // true if Ctrl was held
console.log(e.shiftKey); // true if Shift was held
});key gives you the character typed. code gives you the physical key regardless of layout.
document.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
console.log("Enter pressed!");
}
if (e.ctrlKey && e.key === "s") {
e.preventDefault(); // prevent browser save dialog
console.log("Ctrl+S — saving...");
}
});Input Events
const input = document.querySelector("input");
// Fires on every change as user types
input.addEventListener("input", (e) => {
console.log(e.target.value); // current input value
});
// Fires when input loses focus
input.addEventListener("blur", () => {
console.log("Input lost focus");
});
// Fires when input gains focus
input.addEventListener("focus", () => {
console.log("Input focused");
});
// Fires when value changes and input loses focus
input.addEventListener("change", (e) => {
console.log("Changed to:", e.target.value);
});Use input when you want to react on every keystroke. Use change when you only care about the final value after the user is done.
Window Events
// Page fully loaded
window.addEventListener("load", () => {
console.log("Page fully loaded");
});
// DOM ready — before images and stylesheets load
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM ready");
});
// User scrolls
window.addEventListener("scroll", () => {
console.log(`Scrolled to: ${window.scrollY}px`);
});
// Window resized
window.addEventListener("resize", () => {
console.log(`Window: ${window.innerWidth} x ${window.innerHeight}`);
});DOMContentLoaded fires as soon as the HTML is parsed — before images load. This is usually what you want for initializing JavaScript. load waits for everything including images.
Preventing Default Behavior
Many elements have built-in default behaviors — links navigate, forms submit, right-click shows a menu. Use event.preventDefault() to stop these defaults.
// Prevent link from navigating
const link = document.querySelector("a");
link.addEventListener("click", (e) => {
e.preventDefault();
console.log("Link clicked but not navigated");
});
// Prevent form from submitting and refreshing page
const form = document.querySelector("form");
form.addEventListener("submit", (e) => {
e.preventDefault();
console.log("Form submitted without refresh");
// handle form data with JavaScript instead
});Event Bubbling
When you click an element, the event does not just fire on that element. It bubbles up through every parent element all the way to the top of the DOM.
<div class="container">
<button>Click me</button>
</div>document.querySelector("button").addEventListener("click", () => {
console.log("1. Button clicked");
});
document.querySelector(".container").addEventListener("click", () => {
console.log("2. Container clicked");
});
document.querySelector("body").addEventListener("click", () => {
console.log("3. Body clicked");
});
// When button is clicked, all three fire:
// 1. Button clicked
// 2. Container clicked
// 3. Body clickedYou clicked the button — but the event bubbled up through .container and body. Every listener on the way fires.
Stopping bubbling
Use event.stopPropagation() to stop the event from bubbling further:
document.querySelector("button").addEventListener("click", (e) => {
e.stopPropagation(); // stops here — container and body won't hear it
console.log("Button clicked");
});Bubbling is not usually a problem — most of the time you want it. But knowing it exists helps you understand why event listeners on parent elements sometimes fire unexpectedly.
Event Capturing
Events actually travel in two phases — capture (down from document to target) and bubble (up from target to document).
By default, addEventListener listens in the bubble phase. Pass true as a third argument to listen in the capture phase:
element.addEventListener("click", handler, true); // capture phase
element.addEventListener("click", handler, false); // bubble phase (default)In practice you will rarely use capture phase. The bubble phase handles almost every real use case.
once Option — Fire Only Once
Pass an options object with once: true and the listener automatically removes itself after firing once:
btn.addEventListener("click", () => {
console.log("This only fires once!");
}, { once: true });Useful for things like showing a welcome message or a one-time tutorial hint.
A Real Example — Interactive Card
<div class="card" id="profile-card">
<h2>Ali Hassan</h2>
<p class="bio">JavaScript Developer</p>
<button class="like-btn">♡ Like</button>
<button class="share-btn">Share</button>
</div>const card = document.getElementById("profile-card");
const likeBtn = card.querySelector(".like-btn");
const shareBtn = card.querySelector(".share-btn");
let liked = false;
let likeCount = 0;
likeBtn.addEventListener("click", (e) => {
liked = !liked;
likeCount = liked ? likeCount + 1 : likeCount - 1;
likeBtn.textContent = liked ? `♥ Liked (${likeCount})` : `♡ Like`;
likeBtn.classList.toggle("active", liked);
e.stopPropagation(); // don't bubble to card
});
shareBtn.addEventListener("click", (e) => {
console.log("Profile link copied!");
shareBtn.textContent = "Copied!";
setTimeout(() => {
shareBtn.textContent = "Share";
}, 2000);
e.stopPropagation();
});
card.addEventListener("click", () => {
card.classList.toggle("expanded");
});
// Keyboard shortcut — press L to like
document.addEventListener("keydown", (e) => {
if (e.key === "l" || e.key === "L") {
likeBtn.click(); // programmatically trigger the click event
}
});Multiple events, stopPropagation to prevent bubbling, a programmatic .click() trigger, and a timeout for resetting button text — all working together in a real interactive component.
Summary
addEventListener(type, handler)— the standard way to listen for events- The event object is automatically passed to every handler — use it to get details about the event
event.target— the element that triggered the eventevent.preventDefault()— stops the element's default behaviorevent.stopPropagation()— stops the event from bubbling up- Events bubble upward through parent elements by default
- Common events —
click,dblclick,mouseenter,mouseleave,keydown,input,change,submit,scroll,resize,DOMContentLoaded - Pass
{ once: true }to automatically remove a listener after it fires once - Use
removeEventListener()with a named function to manually remove a listener