Getting our hamburger menu to work

First things first, we need to create a JavaScript file, and add it to the <head> of our index.html file.

Next, we want to do the following:

  • Listen for clicks on the button
  • When a user clicks on it:
    • Update the aria-expanded
    • Show/hide the navigation

Let's start with the getting the aria-expanded toggling first, then we'll go from there.

const hamburgerButton = document.querySelector(
  '[aria-controls="primary-navigation"]'
);
const nav = document.querySelector(".primary-navigation");

hamburgerButton.addEventListener("click", () => {
  // check if the navigation is opened
  const isNavOpened = hamburgerButton.getAttribute("aria-expanded");

  if (isNavOpened === "false") {
    hamburgerButton.setAttribute("aria-expanded", "true");
  } else {
    hamburgerButton.setAttribute("aria-expanded", "false");
  }
});

Even though we're toggling true/false, attributes are always strings. This is why I am using navOpen === 'false' and not simply (navOpen) or (!navOpen).

Visually opening and closing the menu

The easiest thing to do is to use display: none to hide the menu, but how should we do that?

We could add a new class, like .navigation-closed (or a .navigation-opened) and add/remove it with JavaScript.

This is a very common pattern (and very simple to do), but we already have something that's telling us the current state, the aria-expanded on the button, so I don't see the point of adding more classes if we don't have to.

Instead, we can do this:

[aria-expanded="false"] + .primary-navigation {
  display: none;
}

Why I prefer this method

I like using accessibility hooks to enforce the correct use of something.

The only issue with this approach is it does require setting up our HTML in a specific way, but at the same time, the order here makes sense.

If we're keyboard navigating, I want to get to the menu, and then into the menu if it's opened.

An alternative is to use ~ instead of +, if you need to have something in between the two elements.