Learning LibraryWebsite Development LibraryTeens

Events and listeners: make the page respond

A menu that opens, a tab that switches, a fact that appears when you ask. None of that happens until the page starts listening for what the visitor does.

The big idea

An event is something that happens on the page, such as a click; addEventListener runs your function whenever a chosen event fires on an element.

See it in code

1The basics

Three steps: find the elements, listen for a click, toggle a class. toggle adds the class if it's missing and removes it if it's there:

html
<section class="p-6 text-center">
  <button id="fact-button" class="rounded-full border border-violet-400/40 px-5 py-2 text-sm font-semibold text-violet-200 hover:bg-violet-500/10">Did you know?</button>
  <p id="fact" class="mx-auto mt-4 hidden max-w-md text-slate-300">A day on Venus is longer than its year: it spins once every 243 Earth days, but orbits the Sun in 225.</p>
</section>
<script>
  const factButton = document.querySelector("#fact-button");
  const fact = document.querySelector("#fact");
  factButton.addEventListener("click", () => {
    fact.classList.toggle("hidden");
  });
</script>
Click the button, then click it again:

The function passed to addEventListener doesn't run when the page loads. The browser keeps it and calls it each time the button is clicked.

2A step further

Give the function a name and one click can change several elements together. Here it opens the menu and swaps the button's icon:

html
<header class="flex items-center justify-between bg-slate-900 px-6 py-4">
  <p class="text-lg font-bold text-violet-300">COSMOS</p>
  <button id="menu-button" class="rounded-lg border border-white/10 px-3 py-2">
    <span id="open-icon">☰</span>
    <span id="close-icon" class="hidden">×</span>
  </button>
</header>
<nav id="mobile-menu" class="hidden space-y-1 bg-slate-900 px-6 pb-4">
  <a href="#about" class="block rounded-lg px-3 py-2 hover:bg-white/5">About</a>
  <a href="#missions" class="block rounded-lg px-3 py-2 hover:bg-white/5">Missions</a>
  <a href="#gallery" class="block rounded-lg px-3 py-2 hover:bg-white/5">Gallery</a>
</nav>
<script>
  const menuButton = document.querySelector("#menu-button");
  const mobileMenu = document.querySelector("#mobile-menu");
  const openIcon = document.querySelector("#open-icon");
  const closeIcon = document.querySelector("#close-icon");

  function toggleMenu() {
    mobileMenu.classList.toggle("hidden");
    openIcon.classList.toggle("hidden");
    closeIcon.classList.toggle("hidden");
  }

  menuButton.addEventListener("click", toggleMenu);
</script>
Click the menu button to open and close it:

toggleMenu is passed without parentheses. You're handing over the function itself for the browser to call later, not calling it now.

3On COSMOS

For a set of tabs, attach the same handler to every button. Each click clears every tab's state first, then sets the one that was clicked:

html
<section class="p-6">
  <div class="flex gap-2">
    <button data-planet="Mars" class="tab rounded-full bg-violet-600 px-4 py-2 text-sm font-semibold">Mars</button>
    <button data-planet="Jupiter" class="tab rounded-full px-4 py-2 text-sm font-semibold">Jupiter</button>
    <button data-planet="Saturn" class="tab rounded-full px-4 py-2 text-sm font-semibold">Saturn</button>
  </div>
  <p id="panel" class="mt-4 rounded-xl bg-white/5 p-5 text-slate-300">Mars: home to the tallest volcano in the solar system.</p>
</section>
<script>
  const facts = {
    Mars: "Mars: home to the tallest volcano in the solar system.",
    Jupiter: "Jupiter: its Great Red Spot is a storm wider than Earth.",
    Saturn: "Saturn: its rings are mostly chunks of water ice.",
  };
  const tabs = document.querySelectorAll(".tab");
  const panel = document.querySelector("#panel");

  tabs.forEach((tab) => {
    tab.addEventListener("click", () => {
      tabs.forEach((other) => other.classList.remove("bg-violet-600"));
      tab.classList.add("bg-violet-600");
      panel.textContent = facts[tab.dataset.planet];
    });
  });
</script>
Click the planet tabs:

Clear first, then set. Skip the clearing step and every tab you've clicked stays highlighted. Each button carries its planet in a data- attribute, read with tab.dataset.planet.

The same idea, everywhere

Event-driven code runs most interactive software. A game reacts to key presses, a server to incoming requests, a phone app to taps and notifications.

Try it yourself

Add a fourth tab for Neptune with its own fact, and notice the script needs no new listener. Then close the mobile menu when the Escape key is pressed, by listening for "keydown" on document and checking event.key.

The common mistake

Using remove or add where you need toggle. A menu that only ever removes hidden opens once and can never close. For anything that goes both ways, use toggle.

What it unlocks

Events act on elements you found in the DOM. A handler is a function, and the most common thing a click does on a real site is request live data.