Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to make a hamburger menu without JavaScript

Short answer

A hidden checkbox stores the "open" state, a label controls it, and :has() shows the menu:

<style>
  .nav { padding: 8px; background: navy; color: white; }
  .nav input { position: absolute; opacity: 0; }
  .nav label { cursor: pointer; font-size: 24px; }
  .nav input:focus-visible + label { outline: 2px solid orange; }
  .nav .links { display: none; flex-direction: column; gap: 6px; padding-top: 8px; }
  .nav:has(input:checked) .links { display: flex; }
  .nav a { color: white; }
</style>
 
<nav class="nav">
  <input type="checkbox" id="burger" aria-label="Menu"><label for="burger">☰</label>
  <div class="links"><a href="https://example.com">Home</a><a href="https://example.com">Shop</a><a href="https://example.com">About</a></div>
</nav>

Only on mobile

On a wide screen the button is not needed — the menu is always shown:

@media (min-width: 700px) {
  .nav label { display: none; }
  .nav .links { display: flex; flex-direction: row; }
}

A popover variant

The popover attribute gives a menu above the page, closing on Esc and an outside click, and a button with popovertarget tells screen readers whether the menu is open:

<button popovertarget="side" aria-label="Menu">☰</button>
<nav id="side" popover style="margin: 0; height: 100%; padding: 16px;"><a href="https://example.com">Home</a><br><a href="https://example.com">Shop</a></nav>

Common mistake

People hide the checkbox with display: none, and then the menu cannot be opened from the keyboard. Hide it visually, as in the first example, and give it an aria-label, since the "☰" icon tells a screen reader nothing.

Functions used in this recipe

  • <input type="checkbox"> — A checkbox is an independent yes/no choice; a radio button picks one option from a group.
  • :has() — Matches an element that contains something matching the selector in brackets — the "parent selector".
  • :checked — Matches a checked checkbox or radio button, and a selected `<option>`.
  • <label> — A label for a form field.

Thanks for reading!

Donate to the site — we work hard for you.