Lesson 10 of 12

JavaScript Events

A web page spends most of its life waiting. Then the user clicks a button, types in a search box or submits a form, and something should happen. Each of these moments is an event. With JavaScript events you can “listen” for these moments on any element and run a function when they occur. Combined with the DOM skills from DOM Manipulation, this is what makes a page truly interactive.

Listening with addEventListener

The modern way to handle an event is element.addEventListener(type, callback). The first argument is the event name as a string, such as 'click'. The second is the function to run – the event handler or listener. You pass the function itself; you do not call it, so there are no brackets after its name.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Click counter</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
  </style>
</head>
<body>
  <p>Cups of chai today: <strong id="count">0</strong></p>
  <button id="add" type="button">+ Add a cup</button>
  <button id="reset" type="button">Reset</button>

  <script>
const countEl = document.getElementById('count');
const addBtn = document.getElementById('add');
const resetBtn = document.getElementById('reset');
let cups = 0;

function updateDisplay() {
  countEl.textContent = cups;
}

addBtn.addEventListener('click', () => {
  cups++;
  updateDisplay();
});

resetBtn.addEventListener('click', () => {
  cups = 0;
  updateDisplay();
});
  </script>
</body>
</html>

You can attach several listeners to the same element and event, and they all run in the order they were added. To stop listening, call removeEventListener() with the same event name and the same function reference – which is why you need a named function, not an anonymous arrow, if you plan to remove it later.

Why not onclick in the HTML?

Old tutorials put code straight into attributes, like <button onclick="add()">. Avoid this. It mixes behaviour into your HTML (just as inline styles mix design into it), it only allows one handler per event, the function must be global, and many sites block inline code with a Content Security Policy for security. Keeping all listeners in your JavaScript file is cleaner and safer.

Common JavaScript events

EventFires when…
clickAn element is clicked or tapped, or a focused button is activated with Enter or Space
inputThe value of an input, textarea or select changes – on every keystroke
changeA value is committed, e.g. a checkbox is ticked or a field loses focus after editing
submitA form is submitted (fires on the <form>, not on the button)
keydownA key is pressed; event.key tells you which one
focus / blurAn element gains or loses keyboard focus
DOMContentLoadedThe HTML has been fully parsed (listen on document)

Tip: For anything clickable, use a real <button> rather than a <div> with a click listener. Buttons are reachable with the keyboard and announced correctly by screen readers for free. Inside forms, set type="button" on buttons that should not submit the form.

The event object

Your handler automatically receives an event object as its first argument, usually named event or e. It describes what happened: event.target is the element where the event started, event.type is the event name, and keyboard events have event.key. For an input event, event.target.value is the current text in the field.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Input event</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    .warn { color: #c00; font-weight: bold; }
  </style>
</head>
<body>
  <label for="bio">Short bio (max 80 characters)</label><br>
  <textarea id="bio" rows="3" cols="40" maxlength="80"></textarea>
  <p id="status">80 characters left</p>

  <script>
const bio = document.getElementById('bio');
const statusEl = document.getElementById('status');
const max = 80;

bio.addEventListener('input', (event) => {
  const left = max - event.target.value.length;
  statusEl.textContent = `${left} characters left`;
  statusEl.classList.toggle('warn', left < 10);
});
  </script>
</body>
</html>

Handling forms with preventDefault()

Some JavaScript events have a built-in default action. Submitting a form reloads the page (or navigates to the action URL), and clicking a link opens its href. Calling event.preventDefault() cancels that default so you can handle things in JavaScript – for example, validating the form or sending it with fetch. Always listen for submit on the form itself: it catches both a button click and pressing Enter in a field. The form markup itself is covered in HTML Forms.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Form submit</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
  </style>
</head>
<body>
  <form id="signup">
    <label for="phone">Mobile number</label>
    <input id="phone" name="phone" type="tel" value="98765">
    <button type="submit">Register</button>
  </form>
  <p id="message" role="status"></p>

  <script>
const form = document.getElementById('signup');
const message = document.getElementById('message');

form.addEventListener('submit', (event) => {
  event.preventDefault();   // stop the page from reloading

  const phone = form.elements.phone.value.trim();

  if (phone.length !== 10) {
    message.textContent = `Please enter a 10-digit number (you typed ${phone.length}).`;
  } else {
    message.textContent = `Thanks! We will text ${phone}.`;
  }
});
  </script>
</body>
</html>

Bubbling and event delegation

When you click a <li> inside a <ul>, the click event first fires on the <li>, then bubbles up to the <ul>, the <body> and finally the document. You can use this to your advantage: instead of adding a listener to every list item, add one to the parent and check event.target. This is called event delegation, and it also works for items you add later with JavaScript.

const list = document.getElementById('todo');

list.addEventListener('click', (event) => {
  const item = event.target.closest('li');
  if (!item) return;              // click was not on an item
  item.classList.toggle('done');  // strike it through via CSS
});

Your handlers so far use arrow functions, template literals and other newer syntax. The next lesson, Modern JavaScript (ES6+), collects these modern features in one place.

Further reading

Key takeaways

  • Events are moments such as clicks, typing and form submissions.
  • Use element.addEventListener('event', handler) – not inline onclick attributes.
  • The handler receives an event object with target, type and key.
  • Use input for live typing and submit on the form for form handling.
  • event.preventDefault() cancels default actions like page reloads.
  • Events bubble up, so one listener on a parent can handle many children (delegation).

Quick quiz

Check what you learned – pick an answer.

1. Which line correctly runs showMenu when a button is clicked?

2. What does event.preventDefault() do in a submit handler?

3. Which event fires on every keystroke in a text field?

Practice exercise

Create a button labelled "Dark mode" that toggles a class dark on the body element each time it is clicked. Also change the button text to "Light mode" when dark mode is on, and back again when it is off.

Show a hint

classList.toggle() returns true when the class was added – use that value to pick the button text.

Show the solution
<style>
  body.dark { background: #111; color: #eee; }
</style>
<button id="theme" type="button">Dark mode</button>
<script>
  const themeBtn = document.getElementById('theme');

  themeBtn.addEventListener('click', () => {
    const isDark = document.body.classList.toggle('dark');
    themeBtn.textContent = isDark ? 'Light mode' : 'Dark mode';
  });
</script>

Progress is saved in this browser only.