Episode 17 of 19

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-full for positioning
  • invisible opacity-0 hides it; group-hover:visible group-hover:opacity-100 shows 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 hidden or manipulating maxHeight.
  • Swap hamburger/X icons by toggling hidden on two SVGs.
  • Use max-h-0 + overflow-hidden + transition-all for smooth slide animation.
  • CSS-only dropdowns use group + group-hover:visible + opacity transitions.

Next, we will dive into Tailwind transition and animation utilities.

Web DevelopmentJavaScriptTailwind CSSCSSNavigation