Responsive Navbar with a Humburger Menu
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-labelandaria-expandedhelp 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-expandedupdates 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: noneif 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-boxpadding 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
Post a Comment