Code A Program
Building a Responsive Multi-Level Navigation Bar with React, Tailwind CSS, and Framer Motion
Published on August 23, 2025

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:

Bash
npm create vite@latest responsive-nav-bar -- --template react
cd responsive-nav-bar
npm install

Install the required dependencies:

Bash
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:

JavaScript
/** @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:

JSON
{
  "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)

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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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)

JavaScript
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:

JavaScript
{
  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:

CSS
/* Change background colors */
.bg-[#18181A] /* Main nav background */
.bg-[#111]    /* Body background */
.bg-white/5   /* Hover states */

Running the Project

  1. Clone or create the project with the code above

  2. Install dependencies: npm install

  3. Start development server: npm run dev

  4. Open http://localhost:5173 in your browser

Best Practices Implemented

  1. Component Separation: Clear separation between desktop and mobile components

  2. State Management: Efficient use of React hooks for menu states

  3. Performance: Optimized animations with Framer Motion

  4. Accessibility: ARIA labels and keyboard navigation

  5. 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.

Share:
Download Source Code

Get the complete source code for this tutorial. Includes all files, components, and documentation to help you follow along.

View on GitHub

📦What's Included:

  • • Complete source code files
  • • All assets and resources used
  • • README with setup instructions
  • • Package.json with dependencies
💡Free to use for personal and commercial projects