Accessible hamburger menu

Back to list

Hamburger menus are a great way to hide your menus on smaller screens (particularly if you have large sidebars). The problem is they're usually implemented in a way that's inaccessible to keyboard and screen reader users or requires Javascript to even function at all. This one uses the HTML popover tag, so it's easy for accessibility tools to deal with and just needs some CSS to look nice. It plays well with keyboards and screen readers without you having to do anything extra!

Demo

Code

Very unstyled. Feel free to take the code from the demos to make this look nicer!

CSS:

:root {
    --background:#FFF;
    --color:#000;
}

#burger {
    width:100%;
    background:var(--background);
    position:fixed;
    z-index:1000;
}

#burger button {
    color:var(--color);
    padding:5px;
    width:100%;
    display:block;
    text-align:center;
    cursor:pointer;
}

#burger-menu:popover-open {
    height:100vh;
    position:fixed;
    overflow:auto;
    background:var(--background);
    border:0;
}

#burger #menu ul {
    list-style:none;
    margin:0;
    text-align:center;
}

HTML:

<nav id="burger">
<button popovertarget="burger-menu">Menu</button>
<div id="burger-menu" popover="auto">
<ul>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
</ul>
</div>
</nav>

Legacy details version

This is the previous version of the code that uses the details tag. Technically it shouldn't be used for menus, so I would use the current popover version now, but I'm keeping it here for anyone who wants to look at it!

CSS:

:root {
    --background:#FFF;
    --color:#000;
    
    --burgericon:url(/assets/burger.svg); /* Path to burger vector */
    --closeicon:url(/assets/close.svg); /* Path to close vector */
}

#burger {
    width:100%;
    background:var(--background);
    position:fixed;
    z-index:1000;
}

#burger summary {
    list-style-type: none;
    cursor: pointer;
    display:flex;
    align-items:center;
    color:var(--background);
    padding:5px;
}

#burger summary::before, #burger[open]>summary::before {
	height:40px;
	width:40px;
    background:var(--color);
}

#burger summary::-webkit-details-marker {
    display: none;
}

#burger summary::before {
    content: '';
	mask-image:url(/assets/burger.svg);
	-webkit-mask-image:url(/assets/burger.svg);
	mask-size:40px;
	-webkit-mask-size:40px;
}

#burger[open]>summary::before {
    content: '';
	mask-image:url(/assets/close.svg);
	-webkit-mask-image:url(/assets/close.svg);
	mask-size:40px;
	-webkit-mask-size:40px;
}

#burger[open] {
	position:fixed;
}

.skip a {
    position:absolute;
    left:-10000px;
    top:auto;
    width:1px;
    height:1px;
    overflow:hidden;
}
 
.skip a:focus {
    position:static;
    width:auto;
    height:auto;
}

#burger #menu {
    height:calc(100vh - 50px);
    z-index:1000;
    position:relative;
    overflow:auto;
    background:var(--background);
}

#burger #menu ul {
    list-style:none;
    margin:0;
    text-align:center;
}

Javascript:

window.onload = function () {
    let burger = document.getElementById("burger");

    burger.addEventListener("keydown", (event) => {
        let key = event.code;
        if (key == "Escape") {
            burger.removeAttribute("open");
        }
    });
};

HTML:

<details id="burger">
<summary id="toggle">Menu</summary>
<nav id="menu">
<ul>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
    <li><a href="">Link</a></li>
    <li class="skip"><a href="#toggle">Close menu</a></li>
</ul>
</nav>
</details>