Events are how JavaScript responds to user actions — clicks, key presses, form submissions, mouse movements, and more. Understanding events and how to listen for them is the core skill that makes web pages interactive. This article covers everything from basic click handlers to event delegation and stopping propagation.
What is an event?
An event is a signal that something has happened in the browser — a user clicked a button, typed in a field, scrolled the page, or the document finished loading. JavaScript lets you listen for these signals and run a function in response.
There are three ways to attach events to elements. The modern and recommended way is addEventListener().
| // ❌ Old way 1 — inline HTML (avoid) <button onclick="handleClick()">Click</button> // ❌ Old way 2 — event property (only one handler) btn.onclick = function() { ... }; // ✅ Modern way — addEventListener (recommended) btn.addEventListener("click", function() { ... }); |
addEventListener syntax
| element.addEventListener(eventType, handler, options); // eventType — string: "click", "input", "submit", etc. // handler — function to run when event fires // options — optional: { once: true, capture: true } const btn = document.querySelector("#myBtn"); btn.addEventListener("click", function() { console.log("Button clicked!"); }); // Arrow function shorthand btn.addEventListener("click", () => { console.log("Button clicked!"); }); // Named function (reusable + removable) function handleClick() { console.log("Button clicked!"); } btn.addEventListener("click", handleClick); btn.removeEventListener("click", handleClick); |
| You can attach multiple listeners to the same element for the same event with addEventListener — something you cannot do with the onclick property. The listeners fire in the order they were added. |
The event object
When an event fires, the browser automatically passes an event object to your handler function. This object contains useful information about what happened.
| btn.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 console.log(event.timeStamp); → when it happened (ms) }); // event.target vs event.currentTarget // target — the element that was actually clicked // currentTarget — the element the listener is attached to |
Common event types
MOUSE EVENTS
| "click" ← single click "dblclick" ← double click "mousedown" ← mouse button pressed "mouseup" ← mouse button released "mousemove" ← mouse moves over element "mouseover" ← mouse enters element (includes children) "mouseenter" ← mouse enters element (ignores children) "mouseleave" ← mouse leaves element "contextmenu"← right-click |
KEYBOARD EVENTS
| "keydown" ← key pressed (fires repeatedly while held) "keyup" ← key released "keypress" ← deprecated, use keydown instead document.addEventListener("keydown", (e) => { console.log(e.key); → "Enter", "a", "ArrowUp" console.log(e.code); → "KeyA", "Enter", "Space" console.log(e.ctrlKey); → true if Ctrl held console.log(e.shiftKey); → true if Shift held }); |
FORM EVENTS
| "input" ← fires on every keystroke in an input field "change" ← fires when value changes and field loses focus "submit" ← fires when a form is submitted "focus" ← fires when an input is focused "blur" ← fires when an input loses focus |
PAGE AND WINDOW EVENTS
| "DOMContentLoaded" ← HTML parsed, DOM ready "load" ← page fully loaded (images, styles) "scroll" ← user scrolls the page "resize" ← window is resized |
Practical examples
HANDLING A FORM SUBMIT
| const form = document.querySelector("#loginForm"); form.addEventListener("submit", (e) => { e.preventDefault(); ← stop page from reloading const email = document.querySelector("#email").value; const pass = document.querySelector("#password").value; console.log("Submitted:", email, pass); }); |
LIVE INPUT FEEDBACK
| const input = document.querySelector("#search"); const output = document.querySelector("#results"); input.addEventListener("input", (e) => { output.textContent = `Searching: ${e.target.value}`; }); |
KEYBOARD SHORTCUT
| document.addEventListener("keydown", (e) => { if (e.ctrlKey && e.key === "s") { e.preventDefault(); console.log("Saving..."); } }); |
Event bubbling and capturing
When an event fires on an element, it does not stay there. It travels up through parent elements all the way to the document. This is called event bubbling.
| /* HTML: <div id="outer"><button id="inner">Click</button></div> */ document.querySelector("#inner").addEventListener("click", () => { console.log("inner clicked"); }); document.querySelector("#outer").addEventListener("click", () => { console.log("outer clicked"); }); // Click the button → both fire: → inner clicked → outer clicked ← bubbles up to parent |
STOPPING BUBBLING
| document.querySelector("#inner").addEventListener("click", (e) => { e.stopPropagation(); ← stops the event from bubbling up console.log("inner only"); }); |
Event delegation
Instead of attaching a listener to every child element, you can attach one listener to the parent and use event.target to figure out which child was clicked. This is called event delegation — it is more efficient and works even for elements added to the DOM later.
| /* HTML: <ul id="list"><li>Item 1</li><li>Item 2</li></ul> */ // ❌ Bad — listener on every li document.querySelectorAll("li").forEach(li => { li.addEventListener("click", () => console.log(li.textContent)); }); // ✅ Good — one listener on the parent document.querySelector("#list").addEventListener("click", (e) => { if (e.target.tagName === "LI") { console.log(e.target.textContent); } }); |
| Event delegation is the recommended pattern for lists, tables, and any dynamic content where items are added or removed. One listener beats dozens of listeners every time. |
Key takeaways
Always use addEventListener() — it supports multiple listeners and is removable. The event object passed to your handler contains everything you need: type, target, position, and key info. Use e.preventDefault() to stop default browser behaviour (form reload, link navigation). Use e.stopPropagation() to stop an event from bubbling to parent elements. Event bubbling makes event delegation possible — attach one listener to a parent instead of many listeners to each child.