Events & Listeners
Learn how to make your web pages respond to user actions by wiring up event listeners, working with the event object, and understanding how events travel through the DOM.
A static web page is like a brochure — it looks nice, but it doesn't react to you. The moment you want a button to do something when clicked, a form to validate before submitting, or a search box to filter results as you type, you need events. Events are the heartbeat of interactive web development. In this lesson you will learn how to listen for user actions and run your own code in response — this is where JavaScript truly comes alive.
#What Is an Event?
The Doorbell Analogy
Think of your browser as a house. When someone presses the doorbell, a signal fires. If you have set up a listener (you are standing near the door), you hear it and react — open the door, peek through the window, or ignore it. If no one is listening, the doorbell ring disappears into silence. An event is the doorbell press; an event listener is you, deciding what to do when it rings.
The browser fires events constantly — when the user clicks, moves the mouse, presses a key, submits a form, or even just scrolls. Your job is to register a listener on a specific element so your function runs when that event occurs.
#addEventListener — The Core API
addEventListener takes two required arguments: the event name (a string like "click") and a callback function that runs when the event fires. You call it on any DOM element.
const button = document.querySelector('#myButton');
button.addEventListener('click', function () {
console.log('Button was clicked!');
});Arrow functions work just as well and are often shorter:
button.addEventListener('click', () => {
console.log('Arrow function handler fires too!');
});#Common Event Types
There are dozens of event types. Here are the ones you will reach for most often as a beginner:
| Event name | When it fires | |---|---| | click | User clicks an element | | input | Value of an <input> or <textarea> changes | | change | Input loses focus after its value changed | | submit | A <form> is submitted | | keydown | A key is pressed | | mouseover | Mouse moves onto an element |
#The Event Object
Every time an event fires the browser automatically passes an event object to your callback. This object is packed with useful information: which key was pressed, which element was clicked, where the mouse was, and much more. You just name a parameter in your callback to receive it — by convention it is called e or event.
button.addEventListener('click', (e) => {
console.log('Event type:', e.type);
console.log('Clicked element:', e.target);
console.log('Mouse X position:', e.clientX);
});For input events, e.target.value gives you the current text the user has typed — incredibly useful for live search or validation:
const searchBox = document.querySelector('#search');
searchBox.addEventListener('input', (e) => {
console.log('User typed:', e.target.value);
});#event.preventDefault — Stopping Default Browser Behaviour
Some HTML elements have built-in behaviour. Clicking a link navigates the page. Submitting a form reloads the page. Sometimes you want to intercept that action and handle it yourself with JavaScript. That is exactly what event.preventDefault() does.
const form = document.querySelector('#signupForm');
form.addEventListener('submit', (e) => {
e.preventDefault(); // Stop the page from reloading
const name = document.querySelector('#nameInput').value;
if (name.trim() === '') {
console.log('Name cannot be empty!');
} else {
console.log('Form submitted with name:', name);
}
});Forgetting preventDefault on Forms
This is one of the most common beginner mistakes. If you add a submit listener but forget e.preventDefault(), the browser will reload the page the instant the form is submitted — before your JavaScript has a chance to do anything useful. Always call it first thing inside a submit handler.
#Event Bubbling — How Events Travel Upward
Bubbles Rising in Water
Imagine the DOM as a glass of water, with deeply nested elements at the bottom and document at the top. When an event fires on an element, it is like a bubble released at the bottom — it rises up through every ancestor element all the way to the top. Each ancestor that has a listener for that event will also have its handler called.
Bubbling means a click on a <button> inside a <div> will trigger click listeners on the button AND on the div AND on the body AND on the document. This is usually fine, but it can cause surprising double-triggers when you have listeners on nested elements.
// HTML: <div id="outer"><button id="inner">Click</button></div>
const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');
outer.addEventListener('click', () => console.log('Outer div fired!'));
inner.addEventListener('click', () => console.log('Inner button fired!'));If you want to stop an event from bubbling further, call e.stopPropagation() inside your handler. Use this sparingly — it can make code harder to reason about — but it is good to know it exists.
What does event.preventDefault() do when called inside a form's submit listener?
Removing a Listener When You No Longer Need It
You can remove a listener with element.removeEventListener('click', handlerFn). This requires a named function reference — anonymous arrow functions cannot be removed because there is no variable pointing at them. Name your handler if you ever need to clean it up (useful in single-page apps to avoid memory leaks).
Key takeaways
- Use addEventListener(eventName, callback) to respond to user actions on any DOM element.
- The event object (e) is automatically passed to your callback and contains details like e.target, e.type, and e.target.value.
- Call e.preventDefault() to stop the browser's built-in behaviour, such as a form reload on submit.
- Events bubble up through ancestor elements by default — a click on a child also triggers listeners on its parents.
- Common event types you will use daily: click, input, submit, keydown, and change.
The lesson shows that events bubble up from the clicked element to its ancestors. If the user clicks the inner button, what gets logged?
// HTML: <div id="outer"><button id="inner">Click</button></div>
const outer = document.querySelector('#outer');
const inner = document.querySelector('#inner');
outer.addEventListener('click', () => console.log('Outer div fired!'));
inner.addEventListener('click', () => console.log('Inner button fired!'));This code has a bug — what's wrong?
const form = document.querySelector('#signupForm');
form.addEventListener('submit', (e) => {
const name = document.querySelector('#nameInput').value;
console.log('Form submitted with name:', name);
});Complete this live search box. On every keystroke it should log exactly what the user has typed so far. Fill in the event name and the property that holds the input's current text.
const searchBox = document.querySelector('#search'); searchBox.addEventListener('', (e) => { console.log('User typed:', e.target.); });
Put the lines in order to build a form handler that stops the page reloading, reads the typed name, and logs it. The submit listener should be fully assembled in the correct sequence.
const form = document.querySelector('#signupForm');});
const name = document.querySelector('#nameInput').value;e.preventDefault();
console.log('Form submitted with name:', name);form.addEventListener('submit', (e) => {Build a tiny live character counter. There is a textarea with id 'bio' and a paragraph with id 'count'. As the user types in the textarea, update the paragraph to show how many characters have been typed (e.g. '42 characters'). Also wire up a Reset button with id 'resetBtn' that clears the textarea and resets the counter to '0 characters' when clicked.
Try it live — edit the code and hit Run to see the output: