aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Hamburger Menu: Build an Accessible Mobile Nav

On mobile devices the screen is narrow, so a horizontal menu bar rarely fits. This is exactly where the hamburger menu earns its place: a small button made of three lines that, when tapped, reveals the navigation links. In this article we will build an accessible, smooth mobile navigation step by step using plain HTML, CSS and JavaScript, with no libraries at all.

The skeleton: meaningful HTML

A good menu starts with semantic markup that still makes sense when JavaScript is disabled. Write the trigger as a real <button>, not a <div>. Add aria-expanded and aria-controls so screen readers understand the state.

<header class="site-header">
  <a href="/" class="logo">aslain</a>

  <button id="navToggle" class="nav-toggle"
          aria-expanded="false"
          aria-controls="primaryNav"
          aria-label="Open menu">
    <span class="bar"></span>
    <span class="bar"></span>
    <span class="bar"></span>
  </button>

  <nav id="primaryNav" class="nav">
    <ul>
      <li><a href="/#about">About</a></li>
      <li><a href="/#projects">Projects</a></li>
      <li><a href="/#contact">Contact</a></li>
    </ul>
  </nav>
</header>

The three <span class="bar"> elements are the lines of the icon. Drawing them in CSS is lighter than using an image file and lets you control colour and animation with ease.

Drawing the icon with CSS

We use a media query to show the button only on narrow screens. On wide screens the menu is already horizontal and the hamburger button stays hidden.

.nav-toggle {
  display: none;            /* hidden on desktop */
  width: 44px;
  height: 44px;            /* at least a 44px touch target */
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-toggle .bar {
  display: block;
  width: 24px;
  height: 2px;
  margin: 5px auto;
  background: currentColor;
  transition: transform .25s ease, opacity .25s ease;
}

@media (max-width: 760px) {
  .nav-toggle { display: block; }
}

Aim for a touch target of at least 44×44 pixels; it is a safe size that fingers can hit comfortably and the one accessibility guidelines recommend.

The open and close layer

On narrow screens we hide the menu panel by default and reveal it when an .is-open class is added. Using transform and visibility here instead of display: none keeps the slide transition smooth.

@media (max-width: 760px) {
  .nav {
    position: fixed;
    inset: 64px 0 0 0;          /* starts below the header */
    background: #0f0f12;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform .3s ease, visibility .3s;
  }
  .nav.is-open {
    transform: translateX(0);
    visibility: visible;
  }
  .nav ul {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    padding: 2rem;
  }
}

Turning the three lines into an "X" clearly communicates that the menu is open. We do this with CSS alone: the top and bottom bars rotate, the middle one fades away.

.nav-toggle.is-active .bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-toggle.is-active .bar:nth-child(2) {
  opacity: 0;
}
.nav-toggle.is-active .bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

JavaScript: state and accessibility

The only job of JavaScript is to toggle classes and update aria-expanded. Keeping the state in aria-expanded as the single source of truth keeps the code clean.

const toggle = document.getElementById('navToggle');
const nav = document.getElementById('primaryNav');

function setOpen(open) {
  toggle.setAttribute('aria-expanded', String(open));
  toggle.classList.toggle('is-active', open);
  nav.classList.toggle('is-open', open);
  toggle.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
}

toggle.addEventListener('click', () => {
  const open = toggle.getAttribute('aria-expanded') === 'true';
  setOpen(!open);
});

// Close the menu when a link is tapped
nav.addEventListener('click', (e) => {
  if (e.target.closest('a')) setOpen(false);
});

// The Escape key closes the menu
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') setOpen(false);
});

Three small but important details to notice:

  • Close on link click: on a single-page site it is annoying when the menu stays open after the user jumps to a section.
  • Escape support: keyboard users expect the Esc key to close the menu.
  • aria-label update: switching the label between "open" and "close" gives the screen reader the correct information.

Focus management and body scroll

Preventing the page behind the menu from scrolling while it is open is an important detail on mobile. You can add a class to <body> and apply overflow: hidden. Moving focus to the first link when the menu opens also makes the flow natural for keyboard and screen-reader users.

function setOpen(open) {
  /* ...previous lines... */
  document.body.classList.toggle('nav-locked', open);
  if (open) nav.querySelector('a')?.focus();
  else toggle.focus();
}
body.nav-locked { overflow: hidden; }

Returning focus to the hamburger button when the menu closes lets the user continue from where they left off. This small focus-return behaviour is the signature of an accessible component.

Respecting reduced-motion preferences

Some users choose to reduce motion in their operating system settings. Switching off animations to honour that preference is both polite and accessible.

@media (prefers-reduced-motion: reduce) {
  .nav, .nav-toggle .bar { transition: none; }
}

These few lines let motion-sensitive users operate the menu without abrupt transitions, with no loss of functionality.

Frequently Asked Questions

Why should I use a <button> instead of a <div>?

A real <button> is keyboard focusable, works with the Enter and Space keys, and is announced as a "button" to screen readers. With a <div> you have to mimic all of that by hand, which means extra code and a source of bugs.

Is an image or CSS better for the hamburger icon?

In most cases drawing three <span> elements with CSS is better: no extra request, the colour follows the theme via currentColor, and you can animate the morph into an X directly. SVG is a valid choice too.

At which width should the menu collapse?

There is no fixed rule; look at your content. Placing the breakpoint where the horizontal menu starts to no longer fit (often around 700–800px) works well. Test your design by narrowing the browser.

Want your mobile menu to be both elegant and accessible? Let's review your site's navigation together. Get in touch and let's talk about your project.

Bu kategorideki tüm yazılar →

Devamı için