An event is the browser telling your code that something happened: a button was pressed, a field changed, a key went down, the page finished loading. Listening for events is how a page becomes interactive. This lesson covers the essentials and makes the quiz app playable.
Listening for an event
addEventListener takes the event name and a function to call when it happens:
const button = document.querySelector("#next-button");
button.addEventListener("click", () => {
console.log("Next was pressed");
});
The function is called a listener or handler. You pass the function itself; you do not call it. A common mistake is adding brackets:
function sayHello() {
console.log("Hello");
}
button.addEventListener("click", sayHello); // correct: runs on every click
button.addEventListener("click", sayHello()); // wrong: runs once now, and passes undefined
You can add several listeners for the same event to one element. They run in the order they were added.
You may see onclick="..." attributes in HTML or button.onclick = fn in older code. Inline attributes mix script into markup and are blocked by a strict Content Security Policy, and the onclick property allows only one handler. Use addEventListener.
Choosing the right event
| Event | Fires when | Typical use |
|---|---|---|
click | A button or link is activated by mouse, touch or keyboard | Buttons and links |
input | The value of a field changes, on every keystroke or paste | Live feedback, search as you type |
change | A field's value is committed: on blur for text, immediately for checkboxes and selects | Settings, filters |
keydown | A key is pressed | Keyboard shortcuts, Escape to close |
submit | A form is submitted | Forms (next lesson) |
focus, blur | An element gains or loses focus | Showing hints |
click deserves special attention. On a real <button>, pressing Enter or Space also fires click. So one click listener serves mouse, touch, keyboard, switch and voice users. That is one of the main reasons to use <button> rather than a clickable div, which gets none of this. The Web Accessibility Essentials course covers choosing between links and buttons.
The event object
Every listener receives an event object describing what happened. By convention it is named event:
button.addEventListener("click", (event) => {
console.log(event.type); // → click
console.log(event.target); // → the element that was clicked
console.log(event.currentTarget); // → the element the listener is on
});
target and currentTarget are the same here. They differ when the click lands on something inside the element, such as an icon in a button: target is the icon, currentTarget is the button. When you need "the element I attached the listener to", use currentTarget. Lesson 29 explains why the two can differ.
Reading input as it changes
The input event plus event.target.value gives you live feedback. Try this on any page with a labelled text area:
<label for="bio">Short bio</label>
<textarea id="bio" maxlength="140" aria-describedby="bio-count"></textarea>
<p id="bio-count">140 characters left</p>
const bio = document.querySelector("#bio");
const count = document.querySelector("#bio-count");
bio.addEventListener("input", (event) => {
const left = 140 - event.target.value.length;
count.textContent = `${left} characters left`;
});
aria-describedby links the count to the field, so screen readers read it as a description when the field gains focus.
Keyboard events
For keys, read event.key. It holds a readable name such as "a", "Enter", "Escape" or "ArrowDown":
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
console.log("Escape pressed: close whatever is open");
}
});
Use key rather than the old keyCode number, which is deprecated. Avoid single-letter shortcuts on their own: they fire while people use speech input or type in other controls. If you add shortcuts, use a modifier key or let users turn them off.
Stopping the default action
Many events come with a built-in browser behaviour: a link navigates, a form submits and reloads the page, a checkbox toggles. event.preventDefault() cancels that behaviour, so your code can handle it instead:
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Handled in JavaScript; the page did not reload");
});
You will use this constantly in the next lesson. Only prevent defaults you are replacing. A link that no longer navigates, or a key that no longer scrolls, surprises people.
Removing a listener
To remove a listener, pass the same function to removeEventListener. That means it needs a name; an anonymous arrow function cannot be removed:
function logClick() {
console.log("clicked");
}
button.addEventListener("click", logClick);
button.removeEventListener("click", logClick);
For a listener that should run only once, the once option removes it automatically:
button.addEventListener("click", () => console.log("First click only"), { once: true });
The quiz: answering questions
Now the quiz can respond. In lesson 15 you wrote showQuestion and markAnswer. Before adding the listeners, make the question heading in quiz/index.html focusable by script, so focus can be moved to it later:
<h2 id="question-text" tabindex="-1">Question text goes here</h2>
tabindex="-1" lets focus() work on the heading without adding it to the Tab order. Then add this to the end of quiz/quiz.js:
let score = 0;
let answered = false;
function handleAnswer(event) {
if (answered) return;
const button = event.currentTarget;
const question = questions[currentIndex];
const isCorrect = button.textContent === question.answer;
answered = true;
if (isCorrect) score += 1;
optionButtons.forEach((option) => option.setAttribute("aria-disabled", "true"));
markAnswer(button, isCorrect);
}
function showResults() {
progress.textContent = "Quiz complete";
questionText.textContent = `You scored ${score} out of ${questions.length}.`;
document.querySelector(".options").hidden = true;
statusRegion.textContent = "";
nextButton.hidden = true;
questionText.focus();
}
optionButtons.forEach((button) => {
button.addEventListener("click", handleAnswer);
});
nextButton.addEventListener("click", () => {
currentIndex += 1;
answered = false;
if (currentIndex < questions.length) {
showQuestion(currentIndex);
questionText.focus();
} else {
showResults();
}
});
Open the page and play through. Then try it with only the keyboard: Tab to an option, press Enter or Space, Tab to "Next question" and press Enter. A few decisions make that work well:
- One handler for three buttons.
handleAnswerusesevent.currentTargetto know which option was chosen, so the same function serves every button. - aria-disabled, not disabled. After an answer, the options should stop counting. Setting
disabledwould remove the button the user just pressed from the focus order, and in some browsers focus would fall back to the top of the page.aria-disabled="true"tells assistive technology the buttons are unavailable, while theansweredflag makes them do nothing and focus stays put. - Focus moves deliberately. Pressing Next hides the Next button (in
showQuestion) while it still has focus. Without thequestionText.focus()call, keyboard users would be sent back to the start of the page. Moving focus to the new question puts them exactly where they need to be, and screen readers read the question out. - Results are announced as well as shown. The live region from lesson 14 has already announced each answer, and focusing the results heading reads the final score.
The comparison button.textContent === question.answer works because each button's text was set from the same options array that answer matches. Lesson 18 shows how to investigate when a check like this unexpectedly fails.
Summary
addEventListener(type, listener)runs your function each time the event happens; pass the function, do not call it.- On a real
<button>,clickcovers mouse, touch and keyboard, so prefer it to key or mouse-specific events for actions. - The event object describes what happened:
type,target,currentTarget, andkeyfor keyboard events. event.preventDefault()cancels the browser's built-in action, such as a form reloading the page.- Remove listeners with the same named function, or use
{ once: true }, and move focus deliberately when the element with focus disappears.