Responsive Nav (part 2)
Complete the responsive navigation bar with JavaScript toggle functionality, animated hamburger icon, dropdown menus, and smooth transitions.
Adding the JavaScript Toggle
In part 1, we built the structure. Now we will wire up the hamburger button to show/hide the mobile menu using a small JavaScript snippet.
Simple Toggle Script
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
menuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
</script>
Place this script before the closing </body> tag. It toggles the hidden class on the mobile menu element.
Animated Hamburger to X
We can swap the hamburger icon for an X when the menu is open:
<button id="menu-btn" class="md:hidden p-2 rounded-lg hover:bg-gray-100 focus:outline-none">
<!-- Hamburger icon -->
<svg id="icon-open" class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<!-- Close (X) icon -->
<svg id="icon-close" class="w-6 h-6 text-gray-700 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const iconOpen = document.getElementById('icon-open');
const iconClose = document.getElementById('icon-close');
menuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
iconOpen.classList.toggle('hidden');
iconClose.classList.toggle('hidden');
});
</script>
Adding Smooth Transition
Instead of an instant show/hide, animate the mobile menu with Tailwind's transition utilities and a bit of JavaScript:
<!-- Mobile menu with transition classes -->
<div id="mobile-menu" class="md:hidden overflow-hidden transition-all duration-300 ease-in-out max-h-0">
<ul class="space-y-2 pb-4">
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 rounded-lg">Home</a></li>
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 rounded-lg">About</a></li>
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 rounded-lg">Services</a></li>
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600 rounded-lg">Contact</a></li>
</ul>
</div>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
let isOpen = false;
menuBtn.addEventListener('click', () => {
isOpen = !isOpen;
if (isOpen) {
mobileMenu.style.maxHeight = mobileMenu.scrollHeight + 'px';
} else {
mobileMenu.style.maxHeight = '0';
}
});
</script>
Using max-h-0 and overflow-hidden with transition-all duration-300, the menu slides open smoothly when we set maxHeight to the content height.
Dropdown Menu
Add a dropdown to a desktop nav item:
<li class="relative group">
<a href="#" class="text-gray-700 hover:text-indigo-600 font-medium text-sm flex items-center gap-1">
Services
<svg class="w-4 h-4 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</a>
<!-- Dropdown panel -->
<div class="absolute left-0 top-full mt-2 w-48 bg-white rounded-xl shadow-lg border border-gray-100 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
<ul class="py-2">
<li><a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600">Web Design</a></li>
<li><a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600">Development</a></li>
<li><a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600">SEO</a></li>
<li><a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50 hover:text-indigo-600">Marketing</a></li>
</ul>
</div>
</li>
Key techniques:
- Parent is
relative group - Dropdown uses
absolute left-0 top-fullfor positioning invisible opacity-0hides it;group-hover:visible group-hover:opacity-100shows it- The chevron rotates with
group-hover:rotate-180
Close Menu on Window Resize
<script>
window.addEventListener('resize', () => {
if (window.innerWidth >= 768) {
mobileMenu.style.maxHeight = '0';
isOpen = false;
}
});
</script>
Complete Responsive Nav
Here is the full navbar combining everything from parts 1 and 2:
<nav class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-6xl mx-auto px-4">
<div class="flex items-center justify-between h-16">
<a href="#" class="text-xl font-bold text-indigo-600">Brand</a>
<ul class="hidden md:flex items-center space-x-8">
<li><a href="#" class="text-gray-700 hover:text-indigo-600 font-medium text-sm">Home</a></li>
<li><a href="#" class="text-gray-700 hover:text-indigo-600 font-medium text-sm">About</a></li>
<li><a href="#" class="text-gray-700 hover:text-indigo-600 font-medium text-sm">Contact</a></li>
</ul>
<button id="menu-btn" class="md:hidden p-2 rounded-lg hover:bg-gray-100">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
<div id="mobile-menu" class="md:hidden overflow-hidden transition-all duration-300 max-h-0">
<ul class="space-y-2 pb-4">
<li><a href="#" class="block px-4 py-2 rounded-lg hover:bg-indigo-50">Home</a></li>
<li><a href="#" class="block px-4 py-2 rounded-lg hover:bg-indigo-50">About</a></li>
<li><a href="#" class="block px-4 py-2 rounded-lg hover:bg-indigo-50">Contact</a></li>
</ul>
</div>
</div>
</nav>
Recap
- Toggle the mobile menu by adding/removing
hiddenor manipulatingmaxHeight. - Swap hamburger/X icons by toggling
hiddenon two SVGs. - Use
max-h-0+overflow-hidden+transition-allfor smooth slide animation. - CSS-only dropdowns use
group+group-hover:visible+opacitytransitions.
Next, we will dive into Tailwind transition and animation utilities.