DocsHub
DOM

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 clicked

Removing 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.

click fires here first bubbles up bubbles up bubbles up document body div.container button
<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 clicked

You 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).

capture phase going down bubble phase going up document body div.container button — target

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 event
  • event.preventDefault() — stops the element's default behavior
  • event.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

On this page