Skip to main content

Making pages interactive

An event is something that happens on the page — a click, a key press, a form submission, a mouse hover. Event listeners let you run code when these events occur.
const button = document.querySelector("#save-btn");

button.addEventListener("click", () => {
  console.log("Button clicked!");
});
That’s the core pattern: select an element, call addEventListener, pass the event name and a function to run.

addEventListener()

element.addEventListener(eventName, handlerFunction);
const button = document.querySelector("#delete-btn");

// Inline arrow function
button.addEventListener("click", () => {
  console.log("Deleted!");
});

// Named function (easier to remove later)
function handleDelete() {
  console.log("Deleted!");
}
button.addEventListener("click", handleDelete);
You can attach multiple listeners to the same element and event. They all fire in the order they were added.

Common events

EventFires whenUsed on
"click"Element is clickedButtons, links, any element
"dblclick"Element is double-clickedAny element
"input"Input value changes (real-time)<input>, <textarea>, <select>
"change"Input value changes (on blur)<input>, <textarea>, <select>
"submit"Form is submitted<form>
"keydown"Key is pressed<input>, document
"keyup"Key is released<input>, document
"focus"Element gains focus<input>, <textarea>
"blur"Element loses focus<input>, <textarea>
"mouseover"Mouse enters elementAny element
"mouseout"Mouse leaves elementAny element
"scroll"Element is scrolledwindow, scrollable elements
"load"Page/image finishes loadingwindow, <img>

Practical examples

// Click
document.querySelector("#save-btn").addEventListener("click", () => {
  saveData();
});

// Real-time input (search-as-you-type)
document.querySelector("#search").addEventListener("input", (e) => {
  filterResults(e.target.value);
});

// Keyboard shortcut
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") {
    closeModal();
  }
});

// Form submission
document.querySelector("form").addEventListener("submit", (e) => {
  e.preventDefault(); // Don't reload the page
  handleSubmit();
});

The event object

Every event handler receives an event object with details about what happened:
button.addEventListener("click", (event) => {
  console.log(event.type);    // "click"
  console.log(event.target);  // The element that was clicked
  console.log(event.clientX); // Mouse X position
  console.log(event.clientY); // Mouse Y position
});

Key properties

PropertyDescription
event.targetThe element that triggered the event
event.currentTargetThe element the listener is attached to
event.typeEvent name (“click”, “submit”, etc.)
event.preventDefault()Stop the default browser action
event.stopPropagation()Stop the event from bubbling up
event.keyWhich key was pressed (for keyboard events)

event.target — the most useful property

event.target tells you exactly which element was interacted with:
document.querySelector("#user-list").addEventListener("click", (e) => {
  // Which <li> was clicked?
  if (e.target.tagName === "LI") {
    console.log("Clicked user:", e.target.textContent);
  }
});

event.preventDefault() — stop default behavior

Some elements have built-in behaviors. preventDefault() stops them:
// Stop a form from reloading the page
form.addEventListener("submit", (e) => {
  e.preventDefault(); // Don't reload!
  // Handle submission with JavaScript instead
});

// Stop a link from navigating
link.addEventListener("click", (e) => {
  e.preventDefault(); // Don't navigate!
  // Do something else instead
});
Forms reload the page on submit by default. You’ll almost always want e.preventDefault() when handling forms with JavaScript. This is the single most common gotcha in form handling.

Event delegation

Instead of adding a listener to every list item, add one listener to the parent and check event.target:
// ❌ Adding a listener to every item (slow for many items)
document.querySelectorAll(".user-card").forEach(card => {
  card.addEventListener("click", () => {
    console.log("Card clicked");
  });
});

// ✅ One listener on the parent (event delegation)
document.querySelector("#user-list").addEventListener("click", (e) => {
  const card = e.target.closest(".user-card");
  if (card) {
    console.log("Card clicked:", card.dataset.userId);
  }
});
Benefits of event delegation:
  • Works for elements added after the listener is set up (dynamically created elements)
  • One listener instead of hundreds — better performance
  • No need to re-attach listeners when the list changes
element.closest(selector) walks up the DOM tree from the clicked element and returns the first ancestor matching the selector. It’s essential for event delegation — it finds the container you care about, even if the user clicked a child element inside it.

Removing event listeners

To remove a listener, you need a reference to the same function:
function handleClick() {
  console.log("Clicked!");
}

// Add
button.addEventListener("click", handleClick);

// Remove
button.removeEventListener("click", handleClick);
// ❌ This doesn't work — different function references
button.addEventListener("click", () => console.log("Click!"));
button.removeEventListener("click", () => console.log("Click!")); // Not the same function

// ✅ Use a named function
function handler() { console.log("Click!"); }
button.addEventListener("click", handler);
button.removeEventListener("click", handler); // Same function reference

One-time listeners

// Listener fires once, then removes itself
button.addEventListener("click", () => {
  console.log("This only fires once!");
}, { once: true });

What’s next?

You can handle clicks and keyboard events. Let’s put it all together with the most common interactive element — forms.

Form handling

Get form values, validate input, and handle submissions