Responsive Navbar with a Humburger Menu

Illustration of a responsive navbar with a hamburger menu for desktop and mobile using HTML, CSS, and JavaScript.

A navigation bar is one of the first things visitors interact with on any website. On desktop, there's plenty of room to lay out links horizontally. But on mobile screens, that same layout breaks down fast. The solution web developers have relied on for years is the hamburger menu a small icon (☰) that expands into a full navigation menu when tapped.

In this article, we'll build a fully responsive navbar from scratch using plain HTML, CSS, and JavaScript no frameworks required.

What We're Building

  • A horizontal navbar on large screens (desktop/tablet)
  • A collapsible hamburger menu on small screens (mobile)
  • Smooth open/close animation
  • An icon that morphs from "hamburger" to "X" when active
  • Accessible markup (keyboard and screen-reader friendly)

1. The HTML Structure

Keep the markup simple: a logo, a nav list, and a button that toggles the menu.

<nav class="navbar">
  <div class="navbar-container">
    <a href="#" class="navbar-logo">MyBrand</a>

    <button class="navbar-toggle" id="navToggle" aria-label="Toggle navigation" aria-expanded="false">
      <span class="bar"></span>
      <span class="bar"></span>
      <span class="bar"></span>
    </button>

    <ul class="navbar-menu" id="navMenu">
      <li><a href="#">Home</a></li>
      <li><a href="#">About</a></li>
      <li><a href="#">Services</a></li>
      <li><a href="#">Contact</a></li>
    </ul>
  </div>
</nav>

A few things worth noting:

  • The <button> is used instead of a <div> for the toggle, so it's naturally focusable and works with keyboards out of the box.
  • aria-label and aria-expanded help screen readers announce what the button does and whether the menu is open.
  • The three <span class="bar"> elements are the three lines of the hamburger icon — we'll animate them individually.

2. The CSS

Base styles

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.navbar {
  background-color: #1f2937;
  padding: 1rem 1.5rem;
}

.navbar-container {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.navbar-logo {
  color: #ffffff;
  font-size: 1.5rem;
  font-weight: 700;
  text-decoration: none;
}

.navbar-menu {
  display: flex;
  list-style: none;
  gap: 2rem;
}

.navbar-menu li a {
  color: #e5e7eb;
  text-decoration: none;
  font-weight: 500;
  transition: color 0.2s ease;
}

.navbar-menu li a:hover {
  color: #38bdf8;
}

The hamburger button (hidden on desktop)

.navbar-toggle {
  display: none;
  flex-direction: column;
  justify-content: space-between;
  width: 28px;
  height: 20px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  z-index: 20;
}

.navbar-toggle .bar {
  width: 100%;
  height: 3px;
  background-color: #ffffff;
  border-radius: 2px;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

The responsive breakpoint

This is where the real work happens. Below a certain width, we hide the horizontal menu and show the toggle button instead:

@media (max-width: 768px) {
  .navbar-toggle {
    display: flex;
  }

  .navbar-menu {
    position: fixed;
    top: 0;
    right: -100%;
    height: 100vh;
    width: 70%;
    max-width: 300px;
    background-color: #1f2937;
    flex-direction: column;
    align-items: flex-start;
    padding: 5rem 2rem;
    gap: 1.5rem;
    transition: right 0.3s ease;
  }

  .navbar-menu.active {
    right: 0;
  }
}

Animating the icon into an "X"

When the .active class is toggled on the button, we rotate and fade the bars:

.navbar-toggle.active .bar:nth-child(1) {
  transform: translateY(8.5px) rotate(45deg);
}

.navbar-toggle.active .bar:nth-child(2) {
  opacity: 0;
}

.navbar-toggle.active .bar:nth-child(3) {
  transform: translateY(-8.5px) rotate(-45deg);
}

3. The JavaScript

The logic itself is short: toggle a class on click, and update the aria-expanded attribute for accessibility.

const navToggle = document.getElementById('navToggle');
const navMenu = document.getElementById('navMenu');

navToggle.addEventListener('click', () => {
  const isOpen = navMenu.classList.toggle('active');
  navToggle.classList.toggle('active');
  navToggle.setAttribute('aria-expanded', isOpen);
});

// Optional: close the menu when a link is clicked
document.querySelectorAll('.navbar-menu a').forEach(link => {
  link.addEventListener('click', () => {
    navMenu.classList.remove('active');
    navToggle.classList.remove('active');
    navToggle.setAttribute('aria-expanded', false);
  });
});

4. Accessibility Checklist

A hamburger menu is easy to get visually right and easy to get functionally wrong. Before shipping, check that:

  • The toggle button is a real <button>, not a clickable <div>.
  • aria-expanded updates correctly when the menu opens and closes.
  • The menu can be opened, navigated, and closed using only the keyboard (Tab, Enter, Esc).
  • Links inside the menu are still reachable by screen readers even when the menu is visually hidden off-screen (avoid display: none if you want smooth transitions — right: -100% combined with proper ARIA state works better than abruptly hiding content).

5. Common Pitfalls to Avoid

  • Forgetting box-sizing: border-box padding can throw off your width calculations on the mobile menu panel.
  • Not closing the menu on link click users expect the menu to collapse once they've navigated.
  • Relying only on hover for dropdowns inside the mobile menu hover doesn't exist on touchscreens, so any submenus need a tap-friendly interaction too.
  • Ignoring z-index conflicts the toggle button and slide-out panel both need to sit above other page content.

Wrapping Up

A responsive navbar with a hamburger menu is a small component, but it touches almost every core web skill: flexbox layout, media queries, CSS transitions, DOM manipulation, and accessibility. Once you're comfortable with this pattern, it's easy to extend adding dropdown submenus, a search bar, or a sticky header that shrinks on scroll are all natural next steps built on the same foundation.

Comments

Popular posts from this blog

DOM vs HTML Explained 💻

How to Build a Random Quote Generator with JavaScript (Beginner Tutorial)

How to Create Dynamic Elements in JavaScript DOM