Building a Responsive Multi-Level Navigation Bar with React, Tailwind CSS, and Framer Motion
Creating an intuitive and visually appealing navigation bar is crucial for any modern web application. In this comprehensive guide, we'll build a fully responsive multi-level navigation bar that adapts seamlessly between desktop and mobile devices, complete with smooth animations and dropdown menus.
What We'll Build
Our navigation bar will feature:
Responsive design that works on all screen sizes
Multi-level dropdown menus with grid layouts
Smooth animations powered by Framer Motion
Mobile-friendly hamburger menu with collapsible sections
Modern glassmorphism styling with Tailwind CSS
Tech Stack
React 18 - For component-based architecture
Tailwind CSS - For utility-first styling
Framer Motion - For smooth animations
Lucide React - For beautiful icons
Vite - For fast development and building
Youtube Video:
Project Setup
responsive-nav-bar/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ │ ├── logo.webp
│ │ └── react.svg
│ ├── components/
│ │ ├── DesktopMenu.jsx
│ │ └── MobMenu.jsx
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ ├── main.jsx
│ └── utils.js
├── .gitignore
├── eslint.config.js
├── index.html
├── package.json
├── postcss.config.js
├── README.md
├── tailwind.config.js
└── vite.config.js
Let's start by setting up our project structure. First, create a new Vite React project:
npm create vite@latest responsive-nav-bar -- --template react
cd responsive-nav-bar
npm install
Install the required dependencies:
npm install framer-motion lucide-react
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configuration Files
Tailwind Configuration
Update your tailwind.config.js
:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
fontFamily: {
inter: ["Inter", "sans-serif"],
},
},
screens: {
// => @media (max-width: 1279px) { ... }
lg: { min: "925px" },
},
},
plugins: [],
};
Package.json
Your package.json
should include these dependencies:
{
"name": "react-ani-nav",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^11.5.6",
"lucide-react": "^0.445.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.12",
"vite": "^5.4.1"
}
}
Styling Setup
Global Styles (src/index.css)
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply bg-[#111] text-gray-300 font-inter;
}
@layer components {
.sub-menu {
@apply absolute top-[4.2rem] p-[15px] rounded-[6px] origin-[50%_-170px] backdrop-blur bg-white/[0.04];
}
}
@layer utilities {
.flex-center {
@apply flex items-center;
}
.flex-center-between {
@apply flex items-center justify-between;
}
}
Menu Data Structure
Navigation Data (src/utils.js)
This file contains our menu configuration with icons and descriptions:
import { Bolt } from "lucide-react";
import { ShoppingBag } from "lucide-react";
import { BellDot } from "lucide-react";
import { BookOpenText } from "lucide-react";
import { BriefcaseBusiness } from "lucide-react";
import { CircleHelp } from "lucide-react";
import { TriangleAlert } from "lucide-react";
import { Users } from "lucide-react";
import { Lock } from "lucide-react";
import { Dessert } from "lucide-react";
import { ShieldPlus } from "lucide-react";
import { MessageCircle } from "lucide-react";
import { Images } from "lucide-react";
import { Figma } from "lucide-react";
import { Play } from "lucide-react";
import { MapPin } from "lucide-react";
import { Database } from "lucide-react";
import { PanelsTopLeft } from "lucide-react";
import { PanelTop } from "lucide-react";
export const Menus = [
{
name: "Features",
subMenuHeading: ["Design", "Scale"],
subMenu: [
{
name: "Design",
desc: "Responsive design",
icon: PanelsTopLeft,
},
{
name: "Management",
desc: "Site control",
icon: Bolt,
},
{
name: "Navigation",
desc: "Link pages",
icon: PanelTop,
},
{
name: "CMS",
desc: "Management content",
icon: Database,
},
],
gridCols: 2,
},
{
name: "Resources",
subMenuHeading: ["Get started", "Programs", "Recent"],
subMenu: [
{
name: "Markplace",
desc: "Browse templates",
icon: ShoppingBag,
},
{
name: "Meetups",
desc: "Upcoming events",
icon: MapPin,
},
{
name: "Updates",
desc: "Changelog",
icon: BellDot,
},
{
name: "Academy",
desc: "Watch lessions",
icon: Play,
},
{
name: "Blog",
desc: "Posts",
icon: BookOpenText,
},
{
name: "Figma",
desc: "Plugin",
icon: Figma,
},
{
name: "Experts",
desc: "Jobs",
icon: BriefcaseBusiness,
},
{
name: "Gallery",
desc: "Images",
icon: Images,
},
],
gridCols: 3,
},
{
name: "Support",
subMenu: [
{
name: "Help",
desc: "Center",
icon: CircleHelp,
},
{
name: "Community",
desc: "Project help",
icon: MessageCircle,
},
{
name: "Emergency",
desc: "Urgent issues",
icon: TriangleAlert,
},
],
gridCols: 1,
},
{
name: "Enterprise",
subMenuHeading: ["Overview", "Features"],
subMenu: [
{
name: "Enterprise",
desc: "Overview",
icon: ShieldPlus,
},
{
name: "Collaboration",
desc: "Design together",
icon: Users,
},
{
name: "Customers",
desc: "Stories",
icon: Dessert,
},
{
name: "Security",
desc: "Your site secured",
icon: Lock,
},
],
gridCols: 2,
},
{
name: "Pricing",
},
{
name: "Contact",
},
];
Desktop Navigation Component
Desktop Menu (src/components/DesktopMenu.jsx)
This component handles the desktop dropdown menus with hover animations:
import { useState } from "react";
import { ChevronDown } from "lucide-react";
import { motion } from "framer-motion";
export default function DesktopMenu({ menu }) {
const [isHover, toggleHover] = useState(false);
const toggleHoverMenu = () => {
toggleHover(!isHover);
};
const subMenuAnimate = {
enter: {
opacity: 1,
rotateX: 0,
transition: {
duration: 0.5,
},
display: "block",
},
exit: {
opacity: 0,
rotateX: -15,
transition: {
duration: 0.5,
},
transitionEnd: {
display: "none",
},
},
};
const hasSubMenu = menu?.subMenu?.length;
return (
<motion.li
className="group/link"
onHoverStart={() => {
toggleHoverMenu();
}}
onHoverEnd={toggleHoverMenu}
key={menu.name}
>
<span className="flex-center gap-1 hover:bg-white/5 cursor-pointer px-3 py-1 rounded-xl">
{menu.name}
{hasSubMenu && (
<ChevronDown className="mt-[0.6px] group-hover/link:rotate-180 duration-200" />
)}
</span>
{hasSubMenu && (
<motion.div
className="sub-menu"
initial="exit"
animate={isHover ? "enter" : "exit"}
variants={subMenuAnimate}
>
<div
className={`grid gap-7 ${
menu.gridCols === 3
? "grid-cols-3"
: menu.gridCols === 2
? "grid-cols-2"
: "grid-cols-1"
}`}
>
{hasSubMenu &&
menu.subMenu.map((submenu, i) => (
<div className="relative cursor-pointer" key={i}>
{menu.gridCols > 1 && menu?.subMenuHeading?.[i] && (
<p className="text-sm mb-4 text-gray-500">
{menu?.subMenuHeading?.[i]}
</p>
)}
<div className="flex-center gap-x-4 group/menubox">
<div className="bg-white/5 w-fit p-2 rounded-md group-hover/menubox:bg-white group-hover/menubox:text-gray-900 duration-300">
{submenu.icon && <submenu.icon />}
</div>
<div>
<h6 className="font-semibold">{submenu.name}</h6>
<p className="text-sm text-gray-400">{submenu.desc}</p>
</div>
</div>
</div>
))}
</div>
</motion.div>
)}
</motion.li>
);
}
Mobile Navigation Component
Mobile Menu (src/components/MobMenu.jsx)
This component provides a mobile-friendly hamburger menu with collapsible sections:
import { useState } from "react";
import { motion } from "framer-motion";
import { Menu, X, ChevronDown } from "lucide-react";
export default function MobMenu({ Menus }) {
const [isOpen, setIsOpen] = useState(false);
const [clicked, setClicked] = useState(null);
const toggleDrawer = () => {
setIsOpen(!isOpen);
setClicked(null);
};
const subMenuDrawer = {
enter: {
height: "auto",
overflow: "hidden",
},
exit: {
height: 0,
overflow: "hidden",
},
};
return (
<div>
<button className="lg:hidden z-[999] relative" onClick={toggleDrawer}>
{isOpen ? <X /> : <Menu />}
</button>
<motion.div
className="fixed left-0 right-0 top-16 overflow-y-auto h-full bg-[#18181A] backdrop-blur text-white p-6 pb-20"
initial={{ x: "-100%" }}
animate={{ x: isOpen ? "0%" : "-100%" }}
>
<ul>
{Menus.map(({ name, subMenu }, i) => {
const isClicked = clicked === i;
const hasSubMenu = subMenu?.length;
return (
<li key={name} className="">
<span
className="flex-center-between p-4 hover:bg-white/5 rounded-md cursor-pointer relative"
onClick={() => setClicked(isClicked ? null : i)}
>
{name}
{hasSubMenu && (
<ChevronDown
className={`ml-auto ${isClicked && "rotate-180"} `}
/>
)}
</span>
{hasSubMenu && (
<motion.ul
initial="exit"
animate={isClicked ? "enter" : "exit"}
variants={subMenuDrawer}
className="ml-5"
>
{subMenu.map(({ name, icon: Icon }) => (
<li
key={name}
className="p-2 flex-center hover:bg-white/5 rounded-md gap-x-2 cursor-pointer"
>
<Icon size={17} />
{name}
</li>
))}
</motion.ul>
)}
</li>
);
})}
</ul>
</motion.div>
</div>
);
}
Main App Component
App Component (src/App.jsx)
This is our main component that brings everything together:
import { Menus } from "./utils";
import Logo from "./assets/logo.webp";
import DesktopMenu from "./components/DesktopMenu";
import MobMenu from "./components/MobMenu";
export default function App() {
return (
<div>
<header className="h-16 text-[15px] fixed inset-0 flex-center bg-[#18181A] ">
<nav className=" px-3.5 flex-center-between w-full max-w-7xl mx-auto">
<div className="flex-center gap-x-3 z-[999] relative">
<img src={Logo} alt="" className="size-8" />
<h3 className="text-lg font-semibold">Framer</h3>
</div>
<ul className="gap-x-1 lg:flex-center hidden">
{Menus.map((menu) => (
<DesktopMenu menu={menu} key={menu.name} />
))}
</ul>
<div className="flex-center gap-x-5">
<button
aria-label="sign-in"
className="bg-white/5 z-[999] relative px-3 py-1.5 shadow rounded-xl flex-center"
>
Sign In
</button>
<div className="lg:hidden">
<MobMenu Menus={Menus} />
</div>
</div>
</nav>
</header>
</div>
);
}
Entry Point (src/main.jsx)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
Key Features Explained
1. Responsive Design
The navigation automatically switches between desktop and mobile layouts using Tailwind's responsive utilities. The breakpoint is set at 925px for optimal tablet and desktop viewing.
2. Framer Motion Animations
Desktop dropdowns use rotateX animations for a 3D flip effect
Mobile menus slide in from the left with smooth transitions
Chevron icons rotate when submenus are opened
3. Flexible Grid System
The dropdown menus support different grid layouts (1, 2, or 3 columns) based on the gridCols
property in the menu configuration.
4. Glassmorphism Styling
The navigation uses backdrop-blur effects and semi-transparent backgrounds for a modern, sophisticated look.
5. Accessibility Features
Proper ARIA labels
Keyboard navigation support
Focus states for interactive elements
Customization Options
Adding New Menu Items
Simply extend the Menus
array in utils.js
:
{
name: "New Menu",
subMenuHeading: ["Section 1", "Section 2"],
subMenu: [
{
name: "Item Name",
desc: "Item description",
icon: YourIcon,
},
],
gridCols: 2,
}
Styling Customization
Modify the color scheme by updating the CSS variables and Tailwind classes:
/* Change background colors */
.bg-[#18181A] /* Main nav background */
.bg-[#111] /* Body background */
.bg-white/5 /* Hover states */
Running the Project
Clone or create the project with the code above
Install dependencies:
npm install
Start development server:
npm run dev
Open
http://localhost:5173
in your browser
Best Practices Implemented
Component Separation: Clear separation between desktop and mobile components
State Management: Efficient use of React hooks for menu states
Performance: Optimized animations with Framer Motion
Accessibility: ARIA labels and keyboard navigation
Responsive Design: Mobile-first approach with progressive enhancement
Conclusion
This responsive navigation bar demonstrates modern web development practices by combining React's component architecture with Tailwind's utility-first styling and Framer Motion's smooth animations. The result is a professional, accessible, and highly customizable navigation system that works beautifully across all devices.
The modular structure makes it easy to extend with additional features like search bars, user profiles, or shopping carts. You can also easily adapt the styling to match your brand's color scheme and typography.