Skip to content

Creating a Responsive Navigation Menu in Next.js

Published March 27, 2025
3 min read

Creating a Responsive Navigation Menu in Next.js

A well-designed navigation menu is crucial for user experience. In this tutorial, we'll build a responsive navigation menu for Next.js using Tailwind CSS that works perfectly on all devices.

Project Setup

Ensure you have a Next.js project with Tailwind CSS installed. If not, you can set one up with:

npx create-next-app@latest my-nav-project --typescript --tailwind
cd my-nav-project

Building the Navigation Component

Create a new file at src/components/navbar.tsx:

"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";

const navLinks = [
  { href: "/", label: "Home" },
  { href: "/blog", label: "Blog" },
  { href: "/projects", label: "Projects" },
  { href: "/about", label: "About" },
  { href: "/contact", label: "Contact" },
];

export function Navbar() {
  const pathname = usePathname();
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  return (
    <header className="sticky top-0 z-40 w-full border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 bg-opacity-90 dark:bg-opacity-90">
      <div className="container mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex h-16 items-center justify-between">
          <div className="flex items-center">
            <Link href="/" className="flex items-center font-bold text-xl">
              <span>Site Logo</span>
            </Link>
          </div>

          {/* Desktop Navigation */}
          <nav className="hidden md:flex items-center space-x-8">
            {navLinks.map((link) => (
              <Link
                key={link.href}
                href={link.href}
                className={`text-sm font-medium transition-colors hover:text-primary-600 ${pathname === link.href 
                  ? "text-primary-600" 
                  : "text-gray-600 dark:text-gray-300"}`}
              >
                {link.label}
              </Link>
            ))}
          </nav>

          {/* Mobile Menu Button */}
          <button
            className="md:hidden p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
            onClick={() => setIsMenuOpen(!isMenuOpen)}
            aria-label="Toggle menu"
          >
            {!isMenuOpen ? (
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                strokeWidth={1.5}
                stroke="currentColor"
                className="w-6 h-6"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
                />
              </svg>
            ) : (
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                strokeWidth={1.5}
                stroke="currentColor"
                className="w-6 h-6"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  d="M6 18L18 6M6 6l12 12"
                />
              </svg>
            )}
          </button>
        </div>
      </div>

      {/* Mobile Menu */}
      {isMenuOpen && (
        <div className="md:hidden border-t border-gray-200 dark:border-gray-800">
          <div className="container mx-auto px-4 py-3">
            <nav className="flex flex-col space-y-3">
              {navLinks.map((link) => (
                <Link
                  key={link.href}
                  href={link.href}
                  className={`px-3 py-2 rounded-md text-sm font-medium ${pathname === link.href 
                    ? "bg-gray-100 dark:bg-gray-800 text-primary-600" 
                    : "text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"}`}
                  onClick={() => setIsMenuOpen(false)}
                >
                  {link.label}
                </Link>
              ))}
            </nav>
          </div>
        </div>
      )}
    </header>
  );
}

Features Explained

1. Responsive Design

We use Tailwind's responsive modifiers to create different layouts for mobile and desktop:

  • Desktop: Horizontal navigation with spaced links
  • Mobile: Hamburger menu that expands to a vertical navigation panel

2. Active Link Highlighting

Using Next.js's usePathname() hook, we can highlight the active link based on the current route:

const pathname = usePathname();

// Then in the link class:
${pathname === link.href ? "text-primary-600" : "text-gray-600"}

3. Smooth Transitions

We use Tailwind's transition utilities to create smooth hover effects:

className="transition-colors hover:text-primary-600"

4. Accessibility

We ensure the navigation is accessible by:

  • Using semantic HTML (header, nav)
  • Adding aria-label to the toggle button
  • Ensuring sufficient color contrast
  • Making the mobile menu keyboard navigable

Using the Navbar

To use this navigation component, import it in your layout:

// src/app/layout.tsx
import { Navbar } from "@/components/navbar";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Navbar />
        <main>{children}</main>
      </body>
    </html>
  );
}

Enhancing the Navigation

Adding Dropdowns

For more complex sites, you might need dropdown menus. Here's a simplified version you can expand:

function DropdownMenu({ label, items }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="relative">
      <button 
        onClick={() => setIsOpen(!isOpen)}
        className="flex items-center"
      >
        {label}
        <svg className="w-4 h-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path d="M19 9l-7 7-7-7" />
        </svg>
      </button>

      {isOpen && (
        <div className="absolute mt-2 w-48 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5">
          <div className="py-1">
            {items.map((item) => (
              <Link
                key={item.href}
                href={item.href}
                className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
                onClick={() => setIsOpen(false)}
              >
                {item.label}
              </Link>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

Conclusion

This responsive navigation menu provides a solid foundation for your Next.js projects. It's easy to customize with your own styling, and it handles both mobile and desktop layouts elegantly. Remember to adjust the colors, spacing, and other design elements to match your brand's style guide.

Share this article

Related Posts

Author

BytescapeHub Team

Tech enthusiasts writing about gaming, AI, and homelab projects. We share insights, tutorials, and the latest technology news.

Subscribe to our newsletter

Get the latest posts delivered right to your inbox.

Comments

Comments are currently disabled. Check back soon!