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.