JavaScript course · Part 8: Projects · lab 11 of 11
Project: make the business site work
The last project: a menu that opens and closes properly on a phone, a filter that announces its results, a dark mode switch that remembers, and a final pass before you call it done.
By the end you can
- Build a menu toggle that works with a keyboard and a screen reader
- Filter a list live and tell everyone how many results there are
- Respect a reader's system setting, and remember their own choice
- Check a finished feature the way a professional would
Easier if you did first
step 1 of 4
The menu button
On a phone the menu hides behind a button. It is the most common piece of JavaScript on the web and it is got wrong constantly, because there is more to it than showing and hiding.
Four requirements, and the first is the only one most sites manage.
It opens and closes. Use the hidden property rather than a class, so the menu is genuinely gone when closed rather than invisible but still tabbable.
It says what it is doing: aria-expanded true or false on the button, updated every time, and aria-controls naming the menu by id. Without those a screen reader announces a button that apparently does nothing.
Escape closes it, because that is what people press, and focus goes back to the button afterwards, or the keyboard is left stranded in a menu that no longer exists.
And a click anywhere outside closes it, which people expect and nobody writes. Listen on the document, and ignore clicks that landed inside the menu or on the button itself.
Build all four.
function setOpen(open) {
menu.hidden = !open;
button.setAttribute("aria-expanded", String(open));
}
document.addEventListener("click", (event) => {
if (menu.hidden) return;
if (event.target.closest("#menu, #menu-button")) return;
setOpen(false);
});What these words mean
- hidden
- Removes the element for everyone, including the keyboard.
- aria-expanded
- Whether the thing this button controls is open.
- aria-controls
- The id of the element it controls.
- closest()
- Walk up from the clicked element to see whether it was inside something.
- focus()
- Put the keyboard back where it belongs after closing.
Easy to get wrong
Hiding the menu with a class that only moves it off screen
The hidden property
An off-screen menu is still in the tab order, so a keyboard user tabs into links they cannot see and the page appears to lose focus entirely.
A document click listener that closes on every click
Ignoring clicks inside the menu or on the button
Otherwise the click that opened the menu immediately closes it again, and clicking a link inside closes it before the link is followed.
What we check
- The button opens the menu and says so
- Pressing it again closes it
- Escape closes it and gives the keyboard back
- A click outside closes it
- A click inside the menu does not
- The menu is properly hidden, not just moved