Skip to content

The Mobile Nav Toggle

Every lesson up to this point has taught a concept with a small exercise. This module is different — every lesson is a complete, shippable STO feature. You are not practicing; you are building.

The mobile nav toggle is the first feature to ship. A hamburger button opens and closes the navigation on small screens. The feature was previewed in Module 06 — here you build the production-ready version with accessibility and edge-case handling included.

  • Clicking the hamburger button opens the mobile navigation
  • Clicking it again closes it
  • Pressing outside the nav also closes it
  • Screen readers know whether the nav is open via aria-expanded

Before you begin — verify the CSS is in place

Section titled “Before you begin — verify the CSS is in place”

This lesson is purely JavaScript. The HTML and CSS for the hamburger button were written in CSS Foundations M08 L02. Before continuing, open index.html at a narrow viewport and confirm:

  • The hamburger button is visible in the header
  • The nav links are hidden
  • Clicking the button toggles the nav open and closed (basic JS from M06 L02)

If anything is missing, complete CSS M08 L02 and JS M06 L02 first.

const hamburgerBtn = document.querySelector('.hamburger-btn');
const nav = document.querySelector('.site-nav');

Verify both elements exist in your STO HTML before continuing. Open the Console and log each to confirm.

if (hamburgerBtn && nav) {
hamburgerBtn.addEventListener('click', () => {
nav.classList.toggle('nav-open');
});
}

At this point the basic toggle works. Click the hamburger button — the nav should open and close.

aria-expanded tells screen readers whether the controlled element is open or closed. Keep it in sync with the class:

hamburgerBtn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('nav-open');
hamburgerBtn.setAttribute('aria-expanded', isOpen);
});

classList.toggle() returns true if the class was added, false if removed. Pass that boolean directly to setAttribute.

Confirm your button already has aria-expanded="false" set — you added it to all six HTML files in Module 06. If it is missing, add it now:

<button class="hamburger-btn" aria-expanded="false" aria-label="Toggle navigation">
<span></span>
<span></span>
<span></span>
</button>

When the nav is open, clicking anywhere outside it (or outside the button) should close it:

document.addEventListener('click', (event) => {
if (!nav.classList.contains('nav-open')) return;
const clickedInsideNav = nav.contains(event.target);
const clickedButton = hamburgerBtn.contains(event.target);
if (!clickedInsideNav && !clickedButton) {
nav.classList.remove('nav-open');
hamburgerBtn.setAttribute('aria-expanded', 'false');
}
});

element.contains(target) returns true if target is the element or any of its descendants. The guard at the top skips all the checks when the nav is already closed.

const hamburgerBtn = document.querySelector('.hamburger-btn');
const nav = document.querySelector('.site-nav');
if (hamburgerBtn && nav) {
hamburgerBtn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('nav-open');
hamburgerBtn.setAttribute('aria-expanded', isOpen);
});
document.addEventListener('click', (event) => {
if (!nav.classList.contains('nav-open')) return;
const clickedInsideNav = nav.contains(event.target);
const clickedButton = hamburgerBtn.contains(event.target);
if (!clickedInsideNav && !clickedButton) {
nav.classList.remove('nav-open');
hamburgerBtn.setAttribute('aria-expanded', 'false');
}
});
}
  1. On a narrow viewport, click the hamburger — nav opens, aria-expanded becomes true in DevTools
  2. Click the hamburger again — nav closes, aria-expanded becomes false
  3. Open the nav, then click somewhere else on the page — nav closes
  4. Click inside the open nav — nav stays open

This is your first complete, production-ready JavaScript feature on a real website. It uses:

  • classList.toggle() returning the new state
  • setAttribute('aria-expanded', ...) for accessibility
  • element.contains() to detect outside clicks
  • A guard clause to skip work when the nav is already closed

The same pattern — toggle a class, sync an ARIA attribute, handle outside clicks — applies to dropdowns, modals, tooltips, and any other show/hide component you will ever build.