Accessible CSS-only burger menu

I was talking to my friend Samuel about frontend development and the use of JS for "nice-to-have" features and he shared this idea of an animated menu with CSS only, using a checkbox.

I had never done it that way before, usually using JS or jQuery for that kind of stuff, so I decided to try it out but suddently came across accessibility issues.

The basic idea was to use a label to control a checkbox's :checked state and then use that as a reference to animate its sibling containing the menu.

So when the checkbox is :checked, the menu opens up.

Solution 1 - The checkbox method

We wanted to keep the actual checkbox hidden and leave only the label (which we can use to check/uncheck the input) visible.

It took just a bit to make sure it was fully accessible because, while trying to hide the checkbox, I kept making it inaccessible to the keyboard.

This is the solution we got:

HTML


<label for="menu-btn">menu</label>
<input type="checkbox" name="menu-btn" id="menu-btn">

<nav>
    <ul>
        <li>menu item</li>
        <li>menu item</li>
    </ul>
</nav>

      

CSS

On my first attempt, I actually wrapped the label in a button but then I couldn't access it with the keyboard to check the checkbox. So, I used CSS to render the label with a "button" shape and make it visibly clear this was an interactive element.

Then, as mentioned before, the menu's visibility and position had to be set up in both the checkbox's states - default and :checked.

I also used opacity to avoid layout shift. Without that, you'd keep seeing the menu sliding up on load, which was really annoying.


#checkbox-nav {
  overflow: hidden;
  & ul {
    opacity: 0;
    transform: translateY(-200%);
  }
}

input[type="checkbox"]:checked + #checkbox-nav > ul {
  opacity: 1;
  transform: translateY(0);
}

        

And it actually works! You can click or hit space on the Checkbox Menu "button" and it opens/closes the menu.

Solution 2 - The Popover toggle method

But while researching a bit about the <button> element I came accross the popover attribute.

With this attribute, the element remains hidden via display:none until a control element like a <button> opens it.

You can read all about it on the mdn web docs: mdn web docs - popover

In this case, we only needed the button and to add the right props to that and the nav, like the popovertarget on the first referring to the id on the second.

HTML


<button popovertarget="button-nav" popovertargetaction="toggle">
  button menu
</button>

<nav id="button-nav" popover>
    <ul>
        <li>menu item</li>
        <li>menu item</li>
    </ul>
</nav>

        

CSS

The CSS was used only to restyle the popover because, by default, it's rendered like a lightbox, and also to set its position on :popover-open.


#button-nav {
  position: fixed;
  background: none;
  border: 0;
}

#button-nav:popover-open {
  margin: 53px 0 auto auto;
}

          

Final remarks

I'm sure there are an infinite ways of doing this.
These are just two methods I had fun experimenting with.

Between these solutions, I find the second one better to work with.

I liked the fact that the toggle is automatic, saving some css lines checking states and, above all, the interaction is better with both mouse and keyboard.
You can close the menu by simply clicking outside of it or hitting the escape key no matter what's focused on the page, as opposed to the checkbox version where the only way to do it is with the label, by hitting it or pressing space while having it focused.

Another pro factor is that, while navigating using the keyboard, it also skips the menu content if it's closed, unlike the checkbox solution.