Making a Mobile Menu
Build an interactive hamburger menu with JavaScript — toggle navigation, animate the icon, and handle accessibility.
Making a Mobile Menu
Our mobile layout hides the navigation and shows a hamburger button, but it doesn't do anything yet. Let's add JavaScript to toggle the menu and CSS animations to make it feel polished.
The JavaScript Toggle
Create a file js/script.js and add:
// Mobile Menu Toggle
const hamburger = document.getElementById('hamburger');
const mainNav = document.getElementById('main-nav');
hamburger.addEventListener('click', () => {
mainNav.classList.toggle('active');
hamburger.classList.toggle('active');
});
// Close menu when a link is clicked
const navLinks = mainNav.querySelectorAll('a');
navLinks.forEach(link => {
link.addEventListener('click', () => {
mainNav.classList.remove('active');
hamburger.classList.remove('active');
});
});
How It Works
- We select the hamburger button and the nav element
- When the hamburger is clicked, we toggle the
activeclass on both elements - The CSS rule
#main-nav.active { display: block; }shows the menu - When any nav link is clicked, we remove the
activeclass to close the menu
Animating the Hamburger Icon
When the menu is open, let's transform the three bars into an X (close icon):
/* Base hamburger styles (from earlier) */
.hamburger .bar {
display: block;
width: 25px;
height: 3px;
background: #fff;
margin: 5px 0;
transition: all 0.3s ease;
}
/* When active: top bar rotates down */
.hamburger.active .bar:nth-child(1) {
transform: rotate(45deg) translate(5px, 6px);
}
/* When active: middle bar disappears */
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
/* When active: bottom bar rotates up */
.hamburger.active .bar:nth-child(3) {
transform: rotate(-45deg) translate(5px, -6px);
}
Adding a Slide-Down Animation
Instead of the menu just appearing, let's add a smooth slide-down effect:
/* Replace display:none/block with height animation */
#main-nav {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
/* Remove display:none from mobile query */
}
#main-nav.active {
max-height: 500px; /* Must be larger than actual content */
}
Note: Update the mobile media query to use max-height: 0 instead of display: none for smooth animation.
Updated Mobile Nav CSS
@media (max-width: 480px) {
#main-nav {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: #333;
border-top: 1px solid #444;
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease;
}
#main-nav.active {
max-height: 400px;
}
}
Closing the Menu on Outside Click
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!hamburger.contains(e.target) &&
!mainNav.contains(e.target)) {
mainNav.classList.remove('active');
hamburger.classList.remove('active');
}
});
Accessibility (ARIA Attributes)
// Update ARIA attributes
hamburger.addEventListener('click', () => {
const isOpen = mainNav.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
});
And update the HTML button:
<button id="hamburger" class="hamburger"
aria-label="Toggle navigation"
aria-expanded="false">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
Complete JavaScript File
// js/script.js
const hamburger = document.getElementById('hamburger');
const mainNav = document.getElementById('main-nav');
// Toggle menu
hamburger.addEventListener('click', () => {
mainNav.classList.toggle('active');
hamburger.classList.toggle('active');
// Update ARIA
const isOpen = mainNav.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
});
// Close on link click
mainNav.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mainNav.classList.remove('active');
hamburger.classList.remove('active');
hamburger.setAttribute('aria-expanded', 'false');
});
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!hamburger.contains(e.target) &&
!mainNav.contains(e.target)) {
mainNav.classList.remove('active');
hamburger.classList.remove('active');
hamburger.setAttribute('aria-expanded', 'false');
}
});
Key Takeaways
- Use
classList.toggle()to show/hide the menu - The hamburger icon animates to an X using CSS transforms on the three bars
- Use
max-heightwithtransitionfor a smooth slide-down animation (not display) - Close the menu when clicking links or clicking outside
- Always include ARIA attributes for screen reader accessibility