HTML Formatterhtmlformatteronline.com

Responsive navbar

A header that collapses to a hamburger under 820px. No framework, keyboard accessible.

navigationheaderresponsivehamburgermenu
<header class="site-head">
  <a class="brand" href="/">Acme</a>

  <button class="burger" id="burger" aria-expanded="false" aria-controls="primary-nav" aria-label="Open menu">
    <span></span><span></span><span></span>
  </button>

  <nav class="primary" id="primary-nav" aria-label="Main">
    <a href="/work/" aria-current="page">Work</a>
    <a href="/studio/">Studio</a>
    <a href="/journal/">Journal</a>
    <a href="/contact/">Contact</a>
    <a class="cta" href="/enquire/">Start a project</a>
  </nav>
</header>

<main style="padding:40px 24px;font:16px/1.6 system-ui,sans-serif;color:#5b6b83">
  <p>Resize the preview to see the menu collapse.</p>
</main>

How it works

The hamburger is a real <button> and the menu is a real <nav>. That sounds obvious and it is the thing most implementations get wrong — a <div> with a click handler is invisible to keyboards and announces nothing to a screen reader.

aria-expanded does double duty. It tells assistive technology whether the menu is open, and it is also the CSS hook that animates the bars into an X. One attribute, both jobs, no extra class to keep in sync.

aria-controls points at the menu's id so the relationship is explicit rather than positional.

The resize listener matters more than it looks. Open the menu on a phone, rotate to landscape, and without that handler the menu can end up hidden while the button still says it is open. Watching the media query keeps the two in step.

Sticky, not fixed. position: sticky keeps the header in flow, so it does not overlap the first paragraph the way a fixed header does unless you remember to add padding.

Accessibility notes

Keyboard users can tab to the button, press Enter or Space to open, tab through the links and press Escape to close — which also returns focus to the button rather than dumping it at the top of the document.

aria-current="page" marks the current page for screen readers. It is separate from the visual highlight and both should be present.

Motion is disabled under prefers-reduced-motion. The bars still become an X, they just do it instantly.

Making it yours

The breakpoint is in one place — change 820px in both the media query and the resize listener together, or they drift apart.

For a dropdown inside this bar, the dropdown menu template uses the same aria pattern and slots straight in.

Related templates

Check your version

Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.