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.