Level 3, Interactive Building, Chapter 11 of 24
Events & Interactivity
Why this matters
You can already select elements and change them with JavaScript, but so far every change has happened immediately when the script runs, with no way for the user to trigger anything themselves. Real interactivity means your code waits patiently until something happens, a click, a key press, a form submission, and only then springs into action.
This is what events are for. Nearly every interactive feature you've ever used, a button that adds an item to a cart, a menu that opens when tapped, a search box that filters results as you type, is built from the same core idea: listen for an event, then run a function in response.
The lesson
Listening for events
The addEventListener method attaches a function, often called a handler or callback, to an element so that the function runs whenever a specific event occurs on that element. It takes the event's name as a string, like "click" or "input", and the function to run. This builds directly on what you learned about functions: the handler is just an ordinary function, often written as an arrow function, that you're handing off to the browser and saying 'run this later, whenever this event happens', rather than calling it yourself right away.
1const button = document.querySelector("#save-btn");23button.addEventListener("click", () => {4 console.log("Save button was clicked!");5});The event object
When the browser calls your handler, it automatically passes in an event object describing what happened, and you can capture it as a parameter, conventionally named event or e. This object carries useful details: event.target refers to the exact element the event happened on, which matters when several elements share one handler, and event.preventDefault() stops the browser's default behavior for that event, such as a form actually submitting and reloading the page, which you'll use constantly once you start validating forms with JavaScript.
1document.querySelectorAll(".tab").forEach((tab) => {2 tab.addEventListener("click", (event) => {3 console.log("Clicked tab:", event.target.textContent);4 });5});Common event types
"click" fires on mouse clicks or taps, and is the most common event you'll use for buttons and links. "input" fires every time a text field's value changes, ideal for live search or character counters, while "change" fires when a field loses focus after its value changed, better suited to dropdowns and checkboxes where you care about the final choice rather than every keystroke. "submit" fires on a form element when it's submitted, and is almost always paired with event.preventDefault() so you can validate and handle the data with JavaScript instead of letting the browser reload the page. "keydown" fires on any key press and is useful for keyboard shortcuts, like submitting on Enter.
- click: mouse or touch activation, most common for buttons and interactive elements.
- input: fires on every change to a text field's value, great for live feedback as someone types.
- change: fires when a field's value is committed, well suited to selects and checkboxes.
- submit: fires when a form is submitted; almost always paired with event.preventDefault().
Updating the page in response
Events become genuinely useful once you combine them with what you already know about the DOM: selecting elements, changing text, and toggling classes. A typical pattern is to listen for a click, read some current state, decide what should change, and then update the DOM to reflect it, for example toggling a class to open a menu, or reading an input's value and appending a new list item built with createElement. This loop, listen, decide, update, is the essential shape of almost all front-end interactivity, and you'll use it again and again in every remaining chapter.
1const menuButton = document.querySelector("#menu-btn");2const menu = document.querySelector("#menu");34menuButton.addEventListener("click", () => {5 menu.classList.toggle("open");6});Event delegation
Attaching a separate listener to every item in a long, changing list, like a to-do list where items get added and removed, gets messy fast, especially since new items added later wouldn't automatically have a listener attached unless you remember to add one each time. Event delegation solves this by taking advantage of event bubbling, the fact that an event fired on a child element also fires on its ancestors. Instead of listening on every item, you attach a single listener to their shared parent and check event.target inside it to figure out which child was actually interacted with. This one pattern scales to lists of any size and handles items added after the page first loads without any extra work.
1const list = document.querySelector("#task-list");23list.addEventListener("click", (event) => {4 if (event.target.matches("li")) {5 event.target.classList.toggle("done");6 }7});Try it yourself
Wire up a button that adds a new item to a list every time it's clicked, using the text from an input field. Then add a click listener on the list itself that toggles a 'done' class on whichever item was clicked.
Chapter boss project
Boss project: An Interactive To-Do List
Build a to-do list app: a text input, an add button, and a list. Clicking the button (or pressing Enter in the input) should add a new item built from the input's current text, then clear the input for the next entry.
Clicking an item in the list should toggle a 'completed' visual state using a CSS class, and each item should also have a small delete button or icon that removes just that item from the list when clicked.
Use event delegation for the click handling on the list itself, rather than attaching a fresh listener to every single item, so that items added after the page loads still work correctly without extra code.
- Listen for "keydown" on the input and check if event.key === "Enter" to support adding tasks without clicking the button.
- Guard against adding empty tasks by checking input.value.trim() before creating a new list item.
- For deleting, give each item's delete control a class, then check event.target.matches(".delete") inside your delegated handler before calling item.remove().
- Remember event.target is the exact element clicked, which might be a child of the li, so you may need to check event.target.closest("li") to find the whole item.
Level-up checklist
Tick these off once each one is true for you.
