Hamburger Menus — Complete Guide
In this tutorial, you will learn about Hamburger Menus. We cover key concepts, practical examples, and best practices to help you master this topic.
Hamburger menus hide navigation links behind a toggle button on small screens, requiring proper ARIA attributes, focus management, keyboard support, and touch-friendly tap targets.
What You'll Learn
- When to use hamburger menus
- Accessible hamburger pattern
- Animating the hamburger icon
- Slide-in vs overlay vs push menus
- Focus management for mobile menus
- Performance considerations
Why It Matters
- Hamburger menus are the most common mobile navigation pattern
- Poorly implemented hamburger menus confuse and frustrate users
- Accessibility is often overlooked in mobile menu implementations
- The pattern is simple but the details matter
Real-World Use
- A news site uses a slide-in menu from the left
- A shopping site uses a full-screen overlay menu
- A blog uses a push menu that moves content aside
- A corporate site uses a simple dropdown hamburger menu
flowchart LR
A[Hamburger Menu] --> B[Toggle Button]
B --> C[aria-expanded]
C --> D{Menu Type}
D --> E[Slide-In]
D --> F[Overlay]
D --> G[Push]
D --> H[Dropdown]
E --> I[Focus Management]
Hamburger Menu Anatomy
A hamburger menu consists of:
- A toggle button with three horizontal lines (hamburger icon)
- A hidden navigation panel
- JavaScript to toggle visibility and manage focus
- CSS for animation and positioning
Accessibility Requirements
The toggle button must:
- Be a
<button>element - Have
aria-expandedto indicate state - Have
aria-controlsto reference the menu - Have an accessible name (aria-label or visible text)
- Be at least 44x44px for touch
- Manage focus when the menu opens and closes
Code Example: Slide-In Hamburger Menu
<header class="header">
<div class="header-inner">
<a href="/" class="logo">SiteName</a>
<button id="menu-toggle"
class="menu-toggle"
aria-expanded="false"
aria-controls="side-menu"
aria-label="Open navigation menu"
onclick="toggleMenu()">
<span class="hamburger-line" aria-hidden="true"></span>
<span class="hamburger-line" aria-hidden="true"></span>
<span class="hamburger-line" aria-hidden="true"></span>
</button>
</div>
<aside id="side-menu"
class="side-menu"
aria-label="Main navigation"
role="navigation"
hidden>
<div class="side-menu-header">
<button id="menu-close"
class="menu-close"
aria-label="Close navigation menu"
onclick="closeMenu()">
✕
</button>
</div>
<ul class="side-menu-list">
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</aside>
<div id="menu-overlay"
class="menu-overlay"
onclick="closeMenu()"
hidden></div>
</header>
<style>
.header {
position: relative;
background: white;
border-bottom: 1px solid #eee;
}
.header-inner {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
max-width: 1200px;
margin: 0 auto;
}
.menu-toggle {
display: none;
background: none;
border: none;
cursor: pointer;
min-width: 44px;
min-height: 44px;
padding: 8px;
}
.hamburger-line {
display: block;
width: 24px;
height: 2px;
background: #333;
margin: 5px 0;
transition: transform 0.3s, opacity 0.3s;
}
/* Hamburger to X animation */
.menu-toggle[aria-expanded="true"] .hamburger-line:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.menu-toggle[aria-expanded="true"] .hamburger-line:nth-child(2) {
opacity: 0;
}
.menu-toggle[aria-expanded="true"] .hamburger-line:nth-child(3) {
transform: rotate(-45deg) translate(5px, -5px);
}
.side-menu {
position: fixed;
top: 0;
left: -300px; /* Off-screen */
width: 280px;
height: 100%;
background: white;
box-shadow: 2px 0 8px rgba(0,0,0,0.1);
z-index: 200;
transition: left 0.3s ease;
overflow-y: auto;
}
.side-menu:not([hidden]) {
left: 0;
}
.side-menu[hidden] {
display: block; /* Override display: none for animation */
visibility: hidden;
left: -300px;
}
.menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 199;
}
/* Desktop: show horizontal nav instead */
@media (min-width: 769px) {
.menu-toggle { display: none; }
.side-menu {
position: static;
width: auto;
height: auto;
box-shadow: none;
transition: none;
display: flex !important;
visibility: visible;
}
.side-menu[hidden] {
display: flex !important;
visibility: visible;
}
.menu-overlay { display: none !important; }
.menu-close { display: none; }
.side-menu-list {
display: flex;
gap: 1rem;
list-style: none;
}
}
/* Mobile */
@media (max-width: 768px) {
.menu-toggle { display: block; }
.menu-toggle[aria-expanded="false"] + .side-menu {
left: -300px;
}
}
</style>
<script>
let lastFocused = null;
function toggleMenu() {
const toggle = document.getElementById('menu-toggle');
const menu = document.getElementById('side-menu');
const overlay = document.getElementById('menu-overlay');
const isOpen = toggle.getAttribute('aria-expanded') === 'true';
if (isOpen) {
closeMenu();
} else {
openMenu();
}
}
function openMenu() {
const toggle = document.getElementById('menu-toggle');
const menu = document.getElementById('side-menu');
const overlay = document.getElementById('menu-overlay');
lastFocused = document.activeElement || toggle;
toggle.setAttribute('aria-expanded', 'true');
toggle.setAttribute('aria-label', 'Close navigation menu');
menu.hidden = false;
overlay.hidden = false;
document.body.style.overflow = 'hidden';
// Focus the close button
setTimeout(() => {
document.getElementById('menu-close').focus();
}, 100);
}
function closeMenu() {
const toggle = document.getElementById('menu-toggle');
const menu = document.getElementById('side-menu');
const overlay = document.getElementById('menu-overlay');
toggle.setAttribute('aria-expanded', 'false');
toggle.setAttribute('aria-label', 'Open navigation menu');
menu.hidden = true;
overlay.hidden = true;
document.body.style.overflow = '';
// Return focus to toggle
if (lastFocused) {
lastFocused.focus();
}
// Handle Escape key
menu.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeMenu();
});
}
</script>
Expected output: On mobile, the hamburger icon slides the menu in from the left. The backdrop overlay is clickable to close. Focus moves to the close button when the menu opens and returns to the toggle when closed. Animation transforms the hamburger into an X. Desktop shows a horizontal navigation instead.
Code Example: Full-Screen Overlay Menu
<button class="overlay-toggle"
aria-expanded="false"
aria-controls="overlay-menu"
aria-label="Menu"
onclick="toggleOverlayMenu()">
<span class="hamburger" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</span>
</button>
<nav id="overlay-menu"
class="overlay-menu"
aria-label="Main navigation"
hidden>
<ul class="overlay-list">
<li><a href="/" onclick="closeOverlayMenu()">Home</a></li>
<li><a href="/about" onclick="closeOverlayMenu()">About</a></li>
<li><a href="/work" onclick="closeOverlayMenu()">Work</a></li>
<li><a href="/contact" onclick="closeOverlayMenu()">Contact</a></li>
</ul>
</nav>
<style>
.overlay-menu {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
opacity: 0;
transition: opacity 0.3s;
}
.overlay-menu:not([hidden]) {
opacity: 1;
}
.overlay-menu[hidden] {
display: flex; /* Override for animation */
pointer-events: none;
}
.overlay-list {
list-style: none;
text-align: center;
}
.overlay-list a {
color: white;
font-size: clamp(1.5rem, 5vw, 3rem);
text-decoration: none;
display: block;
padding: 1rem 2rem;
min-height: 44px;
transition: color 0.2s;
}
.overlay-list a:hover,
.overlay-list a:focus {
color: #66b0ff;
}
</style>
Expected output: The hamburger opens a full-screen overlay menu with large, centered links. The overlay fades in smoothly. Links close the menu when clicked.
Common Mistakes
- Hiding the hamburger menu behind a non-button element — Div or span as toggle is not keyboard accessible.
- Missing focus management — When the menu opens, focus should move inside. When it closes, focus should return.
- No close button inside the menu — Users should be able to close the menu from within, not just by toggling the hamburger again.
- Menu that animates too slowly — 200-300ms is ideal. Longer animations feel sluggish.
- Body scrolling when menu is open — Prevent body scroll with overflow: hidden to maintain the menu context.
- Not closing the menu on Escape or backdrop click — Users expect these standard behaviors.
- Menu that does not close when a link is clicked — Closing the menu on navigation is standard behavior.
Practice Questions
- What two ARIA attributes must a hamburger toggle button have? aria-expanded (true/false) and aria-controls (referencing the menu element id).
- Where should focus go when a hamburger menu opens? Inside the menu, typically to the first link or a close button.
- Where should focus return when a hamburger menu closes? To the toggle button that opened it.
- What CSS property prevents body scrolling when a menu is open? overflow: hidden on the body element.
- Challenge: Build a responsive navigation system with three menu styles: slide-in from the right, full-screen overlay centered, and push menu (pushes content aside). Each must include proper hamburger animation, focus management, Escape key support, backdrop overlay with click-to-close, and keyboard navigation. Test with screen reader.
FAQ
Mini Project
Build a complete responsive navigation with three variations on separate pages. Page 1: Slide-in left menu with hamburger animation, close button, overlay, and sub-navigation accordion. Page 2: Full-screen overlay menu with animated link entrance. Page 3: Push menu that slides the content aside. All three must have: proper ARIA attributes, focus management (open, close, Escape, Tab trap), keyboard navigation, body scroll prevention, smooth 300ms CSS transitions, 44px touch targets, and screen reader support. Include desktop horizontal fallbacks for all three.
What's Next
Continue with Lesson 12: Responsive Tables to learn how to make data tables readable on small screens.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro