Build a Responsive Navbar with Tailwind CSS
Kamis, 16 Jan 2025
A well-designed Tailwind CSS navbar is essential for any modern website. In this tutorial, you’ll learn how to build a fully responsive navigation bar that works seamlessly across all devices.
Basic Navbar Structure
Start with a simple horizontal navbar layout:
<nav class="bg-white shadow-lg">
<div class="max-w-7xl mx-auto px-4">
<div class="flex justify-between items-center h-16">
<!-- Logo -->
<a href="/" class="text-2xl font-bold text-gray-800">
Logo
</a>
<!-- Navigation Links -->
<div class="hidden md:flex space-x-8">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Services</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
</div>
</div>
</div>
</nav>
Key Tailwind classes used:
max-w-7xl mx-auto— Centers content with max widthflex justify-between items-center— Spaces logo and linkshidden md:flex— Hides links on mobile, shows on medium screensspace-x-8— Adds horizontal spacing between links
Mobile Hamburger Menu
Add a hamburger button that appears on mobile:
<nav class="bg-white shadow-lg">
<div class="max-w-7xl mx-auto px-4">
<div class="flex justify-between items-center h-16">
<!-- Logo -->
<a href="/" class="text-2xl font-bold text-gray-800">Logo</a>
<!-- Desktop Navigation -->
<div class="hidden md:flex space-x-8">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Services</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
</div>
<!-- Mobile Menu Button -->
<button id="menu-btn" class="md:hidden flex flex-col space-y-1.5">
<span class="block w-6 h-0.5 bg-gray-800"></span>
<span class="block w-6 h-0.5 bg-gray-800"></span>
<span class="block w-6 h-0.5 bg-gray-800"></span>
</button>
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="hidden md:hidden pb-4">
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Services</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Contact</a>
</div>
</div>
</nav>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
menuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
</script>
The hamburger icon uses three span elements styled as horizontal lines. The md:hidden class ensures it only appears on mobile devices.
Sticky Navbar Implementation
Make the navbar stick to the top when scrolling:
<nav class="bg-white shadow-lg sticky top-0 z-50">
<!-- navbar content -->
</nav>
For a fixed navbar that’s always visible:
<nav class="bg-white shadow-lg fixed top-0 left-0 right-0 z-50">
<!-- navbar content -->
</nav>
<!-- Add padding to body to prevent content from hiding behind navbar -->
<div class="pt-16">
<!-- page content -->
</div>
Navbar with Background Change on Scroll
<nav id="navbar" class="fixed top-0 left-0 right-0 z-50 transition-all duration-300 bg-transparent">
<div class="max-w-7xl mx-auto px-4">
<div class="flex justify-between items-center h-16">
<a href="/" class="text-2xl font-bold text-white" id="logo">Logo</a>
<div class="hidden md:flex space-x-8">
<a href="#" class="text-white hover:text-gray-200">Home</a>
<a href="#" class="text-white hover:text-gray-200">About</a>
<a href="#" class="text-white hover:text-gray-200">Contact</a>
</div>
</div>
</div>
</nav>
<script>
const navbar = document.getElementById('navbar');
const logo = document.getElementById('logo');
const links = navbar.querySelectorAll('a:not(#logo)');
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar.classList.remove('bg-transparent');
navbar.classList.add('bg-white', 'shadow-lg');
logo.classList.remove('text-white');
logo.classList.add('text-gray-800');
links.forEach(link => {
link.classList.remove('text-white', 'hover:text-gray-200');
link.classList.add('text-gray-600', 'hover:text-gray-900');
});
} else {
navbar.classList.add('bg-transparent');
navbar.classList.remove('bg-white', 'shadow-lg');
logo.classList.add('text-white');
logo.classList.remove('text-gray-800');
links.forEach(link => {
link.classList.add('text-white', 'hover:text-gray-200');
link.classList.remove('text-gray-600', 'hover:text-gray-900');
});
}
});
</script>
Dropdown Menus
Add dropdown menus for nested navigation:
<nav class="bg-white shadow-lg">
<div class="max-w-7xl mx-auto px-4">
<div class="flex justify-between items-center h-16">
<a href="/" class="text-2xl font-bold text-gray-800">Logo</a>
<div class="hidden md:flex space-x-8">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<!-- Dropdown -->
<div class="relative group">
<button class="text-gray-600 hover:text-gray-900 flex items-center">
Services
<svg class="w-4 h-4 ml-1" 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" />
</svg>
</button>
<!-- Dropdown Menu -->
<div class="absolute left-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300">
<a href="#" class="block px-4 py-2 text-gray-600 hover:bg-gray-100">Web Design</a>
<a href="#" class="block px-4 py-2 text-gray-600 hover:bg-gray-100">Development</a>
<a href="#" class="block px-4 py-2 text-gray-600 hover:bg-gray-100">SEO</a>
<a href="#" class="block px-4 py-2 text-gray-600 hover:bg-gray-100">Marketing</a>
</div>
</div>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
</div>
</div>
</div>
</nav>
The dropdown uses Tailwind’s group and group-hover utilities for CSS-only hover effects. No JavaScript required!
Animation and Transitions
Animated Hamburger Icon
Transform the hamburger into an X when clicked:
<button id="menu-btn" class="md:hidden flex flex-col space-y-1.5 group">
<span id="line1" class="block w-6 h-0.5 bg-gray-800 transition-all duration-300 origin-center"></span>
<span id="line2" class="block w-6 h-0.5 bg-gray-800 transition-all duration-300"></span>
<span id="line3" class="block w-6 h-0.5 bg-gray-800 transition-all duration-300 origin-center"></span>
</button>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const line1 = document.getElementById('line1');
const line2 = document.getElementById('line2');
const line3 = document.getElementById('line3');
let isOpen = false;
menuBtn.addEventListener('click', () => {
isOpen = !isOpen;
mobileMenu.classList.toggle('hidden');
if (isOpen) {
line1.classList.add('rotate-45', 'translate-y-2');
line2.classList.add('opacity-0');
line3.classList.add('-rotate-45', '-translate-y-2');
} else {
line1.classList.remove('rotate-45', 'translate-y-2');
line2.classList.remove('opacity-0');
line3.classList.remove('-rotate-45', '-translate-y-2');
}
});
</script>
Slide-Down Mobile Menu
<div id="mobile-menu" class="md:hidden overflow-hidden max-h-0 transition-all duration-300 ease-in-out">
<div class="pb-4">
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Services</a>
<a href="#" class="block py-2 text-gray-600 hover:text-gray-900">Contact</a>
</div>
</div>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
menuBtn.addEventListener('click', () => {
if (mobileMenu.style.maxHeight) {
mobileMenu.style.maxHeight = null;
} else {
mobileMenu.style.maxHeight = mobileMenu.scrollHeight + 'px';
}
});
</script>
React Component Version
Here’s a complete React component with TypeScript:
import { useState, useEffect } from 'react';
interface NavLink {
label: string;
href: string;
children?: NavLink[];
}
const navLinks: NavLink[] = [
{ label: 'Home', href: '/' },
{
label: 'Services',
href: '#',
children: [
{ label: 'Web Design', href: '/services/design' },
{ label: 'Development', href: '/services/development' },
{ label: 'SEO', href: '/services/seo' },
]
},
{ label: 'About', href: '/about' },
{ label: 'Contact', href: '/contact' },
];
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled ? 'bg-white shadow-lg' : 'bg-transparent'
}`}>
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<a
href="/"
className={`text-2xl font-bold transition-colors ${
isScrolled ? 'text-gray-800' : 'text-white'
}`}
>
Logo
</a>
{/* Desktop Navigation */}
<div className="hidden md:flex space-x-8">
{navLinks.map((link) => (
<div key={link.label} className="relative group">
<a
href={link.href}
className={`flex items-center transition-colors ${
isScrolled
? 'text-gray-600 hover:text-gray-900'
: 'text-white hover:text-gray-200'
}`}
onMouseEnter={() => link.children && setActiveDropdown(link.label)}
onMouseLeave={() => setActiveDropdown(null)}
>
{link.label}
{link.children && (
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</a>
{/* Dropdown */}
{link.children && (
<div
className={`absolute left-0 mt-2 w-48 bg-white rounded-md shadow-lg transition-all duration-300 ${
activeDropdown === link.label
? 'opacity-100 visible'
: 'opacity-0 invisible'
}`}
onMouseEnter={() => setActiveDropdown(link.label)}
onMouseLeave={() => setActiveDropdown(null)}
>
{link.children.map((child) => (
<a
key={child.label}
href={child.href}
className="block px-4 py-2 text-gray-600 hover:bg-gray-100"
>
{child.label}
</a>
))}
</div>
)}
</div>
))}
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="md:hidden flex flex-col space-y-1.5"
aria-label="Toggle menu"
aria-expanded={isOpen}
>
<span className={`block w-6 h-0.5 transition-all duration-300 origin-center ${
isScrolled ? 'bg-gray-800' : 'bg-white'
} ${isOpen ? 'rotate-45 translate-y-2' : ''}`} />
<span className={`block w-6 h-0.5 transition-all duration-300 ${
isScrolled ? 'bg-gray-800' : 'bg-white'
} ${isOpen ? 'opacity-0' : ''}`} />
<span className={`block w-6 h-0.5 transition-all duration-300 origin-center ${
isScrolled ? 'bg-gray-800' : 'bg-white'
} ${isOpen ? '-rotate-45 -translate-y-2' : ''}`} />
</button>
</div>
{/* Mobile Menu */}
<div className={`md:hidden overflow-hidden transition-all duration-300 ease-in-out ${
isOpen ? 'max-h-96' : 'max-h-0'
}`}>
<div className="pb-4 bg-white rounded-b-lg">
{navLinks.map((link) => (
<div key={link.label}>
<a
href={link.href}
className="block py-2 px-4 text-gray-600 hover:bg-gray-100"
>
{link.label}
</a>
{link.children?.map((child) => (
<a
key={child.label}
href={child.href}
className="block py-2 px-8 text-gray-500 hover:bg-gray-100"
>
{child.label}
</a>
))}
</div>
))}
</div>
</div>
</div>
</nav>
);
}
Accessibility Considerations
A proper Tailwind CSS navbar must be accessible to all users. Follow these guidelines:
Semantic HTML
<nav aria-label="Main navigation">
<!-- navigation content -->
</nav>
Keyboard Navigation
Ensure all interactive elements are focusable:
<a href="#" class="text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded">
Home
</a>
ARIA Attributes
<!-- Mobile menu button -->
<button
id="menu-btn"
aria-label="Toggle navigation menu"
aria-expanded="false"
aria-controls="mobile-menu"
class="md:hidden"
>
<!-- hamburger icon -->
</button>
<!-- Mobile menu -->
<div id="mobile-menu" aria-hidden="true" class="hidden">
<!-- menu items -->
</div>
<script>
menuBtn.addEventListener('click', () => {
const isExpanded = menuBtn.getAttribute('aria-expanded') === 'true';
menuBtn.setAttribute('aria-expanded', !isExpanded);
mobileMenu.setAttribute('aria-hidden', isExpanded);
mobileMenu.classList.toggle('hidden');
});
</script>
Skip Link
Add a skip link for keyboard users:
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-white px-4 py-2 z-50">
Skip to main content
</a>
<nav><!-- navbar --></nav>
<main id="main-content">
<!-- page content -->
</main>
Focus Trap for Mobile Menu
When the mobile menu is open, trap focus within it:
const mobileMenu = document.getElementById('mobile-menu');
const focusableElements = mobileMenu.querySelectorAll('a, button');
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
mobileMenu.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
if (e.key === 'Escape') {
// Close menu
menuBtn.click();
menuBtn.focus();
}
});
Complete Example
Here’s a production-ready navbar combining all features:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Navbar</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<!-- Skip Link -->
<a href="#main" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-blue-600 text-white px-4 py-2 rounded z-[60]">
Skip to main content
</a>
<nav id="navbar" class="fixed top-0 left-0 right-0 z-50 transition-all duration-300 bg-gray-900" aria-label="Main navigation">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- Logo -->
<a href="/" class="text-2xl font-bold text-white">
Brand
</a>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8">
<a href="/" class="text-gray-300 hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 rounded">
Home
</a>
<!-- Dropdown -->
<div class="relative group">
<button class="text-gray-300 hover:text-white transition-colors flex items-center focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 rounded">
Services
<svg class="w-4 h-4 ml-1 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" />
</svg>
</button>
<div class="absolute left-0 mt-2 w-48 bg-white rounded-lg shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform group-hover:translate-y-0 translate-y-2">
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-t-lg">Web Design</a>
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-gray-100">Development</a>
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-gray-100">SEO</a>
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-b-lg">Marketing</a>
</div>
</div>
<a href="/about" class="text-gray-300 hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 rounded">
About
</a>
<a href="/contact" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900">
Contact
</a>
</div>
<!-- Mobile Menu Button -->
<button
id="menu-btn"
class="md:hidden p-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-white"
aria-label="Toggle navigation menu"
aria-expanded="false"
aria-controls="mobile-menu"
>
<span id="line1" class="block w-6 h-0.5 bg-white transition-all duration-300 origin-center"></span>
<span id="line2" class="block w-6 h-0.5 bg-white transition-all duration-300 mt-1.5"></span>
<span id="line3" class="block w-6 h-0.5 bg-white transition-all duration-300 mt-1.5 origin-center"></span>
</button>
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="md:hidden overflow-hidden max-h-0 transition-all duration-300" aria-hidden="true">
<div class="py-4 space-y-2">
<a href="/" class="block py-2 px-4 text-gray-300 hover:text-white hover:bg-gray-800 rounded-lg">Home</a>
<a href="#" class="block py-2 px-4 text-gray-300 hover:text-white hover:bg-gray-800 rounded-lg">Services</a>
<a href="/about" class="block py-2 px-4 text-gray-300 hover:text-white hover:bg-gray-800 rounded-lg">About</a>
<a href="/contact" class="block py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-center">Contact</a>
</div>
</div>
</div>
</nav>
<main id="main" class="pt-16">
<!-- Hero Section for demo -->
<div class="h-screen bg-gradient-to-br from-blue-600 to-purple-700 flex items-center justify-center">
<h1 class="text-4xl md:text-6xl font-bold text-white text-center">
Responsive Navbar Demo
</h1>
</div>
<div class="h-screen bg-gray-100 flex items-center justify-center">
<p class="text-xl text-gray-600">Scroll to see navbar effect</p>
</div>
</main>
<script>
const menuBtn = document.getElementById('menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const line1 = document.getElementById('line1');
const line2 = document.getElementById('line2');
const line3 = document.getElementById('line3');
const navbar = document.getElementById('navbar');
let isOpen = false;
// Mobile menu toggle
menuBtn.addEventListener('click', () => {
isOpen = !isOpen;
menuBtn.setAttribute('aria-expanded', isOpen);
mobileMenu.setAttribute('aria-hidden', !isOpen);
if (isOpen) {
mobileMenu.style.maxHeight = mobileMenu.scrollHeight + 'px';
line1.classList.add('rotate-45', 'translate-y-2');
line2.classList.add('opacity-0');
line3.classList.add('-rotate-45', '-translate-y-2');
} else {
mobileMenu.style.maxHeight = null;
line1.classList.remove('rotate-45', 'translate-y-2');
line2.classList.remove('opacity-0');
line3.classList.remove('-rotate-45', '-translate-y-2');
}
});
// Scroll effect
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar.classList.remove('bg-gray-900');
navbar.classList.add('bg-white', 'shadow-lg');
document.querySelectorAll('#navbar a:not(.bg-blue-600)').forEach(link => {
link.classList.remove('text-gray-300', 'text-white');
link.classList.add('text-gray-600', 'hover:text-gray-900');
});
[line1, line2, line3].forEach(line => {
line.classList.remove('bg-white');
line.classList.add('bg-gray-800');
});
} else {
navbar.classList.add('bg-gray-900');
navbar.classList.remove('bg-white', 'shadow-lg');
document.querySelectorAll('#navbar a:not(.bg-blue-600)').forEach(link => {
link.classList.add('text-gray-300');
link.classList.remove('text-gray-600', 'hover:text-gray-900');
});
[line1, line2, line3].forEach(line => {
line.classList.add('bg-white');
line.classList.remove('bg-gray-800');
});
}
});
// Close menu on escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isOpen) {
menuBtn.click();
}
});
</script>
</body>
</html>
Conclusion
Building a responsive Tailwind CSS navbar involves combining utility classes for layout, responsive design, and interactivity. The key takeaways:
- Use
hidden md:flexto toggle between mobile and desktop layouts - Leverage Tailwind’s
groupandgroup-hoverfor CSS-only dropdowns - Add
sticky top-0orfixed top-0for scroll-persistent navigation - Implement smooth animations with
transition-all duration-300 - Always include proper ARIA attributes for accessibility
With these patterns, you can create professional navigation bars that work flawlessly across all devices and screen sizes.