Introduction
Have you ever found yourself stuck in the complexity of creating a working and responsive navbar with multilevel dropdown menus? If so, you’re not alone. So I've come up with this tutorial to help you create a beautiful navbar for your next project. Before we start, check out a demo of the navigation bar we’ll be building in this tutorial.
It's interesting, isn't it? If you want the complete source code of the Navbar app, click here for the GitHub repository. In order to follow along with this tutorial, make sure to have the following prerequisites:
- Basic knowledge of HTML, CSS, Javascript and React.
- Node Js, NPM and a Code Editor installed
I hope you're ready with a basic knowledge of these technologies and tools on your computer. Along with these, we will be using Tailwind CSS for styling. If Tailwind CSS is new to you, don’t worry; this tuorial will help you understand the basics of it. In fact, you may start using Tailwind for all your future projects.
Apart from Tailwind, we'll also use React, React-DOM, material-ui for icons and react-router-dom to make our Navbar functional. With all this set up, let's start building it step by step.
Project Setup with Tailwind CSS Installation
We're using the more fast and efficient Vite to start the project. Run the following commands in your terminal to get started:
npm create vite@latest react-responsive-navbar -- --template react
cd react-responsive-navbar
npm install
npm run dev
Once you start the development server, you'll see the Vite's default screen running on the default port. Now, clean up the project and remove unnecessary files. Then install the following dependencies in the root of the project directory:
// tailwind css and its peer development dependency 👇
npm install -D tailwindcss postcss autoprefixer
// React router dom 👇
npm install react-router-dom
// MUI for Icons 👇
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
After successfully installing the necessary dependencies, let's configure the Tailwind CSS in our project. Tailwind CSS requires a tailwind.config.js file to customize its default settings and to include any additional plugins or styles we might need. To generate this configuration file, run the following command in the root of the project directory:
npx tailwindcss init -p
Add the paths to your template file in this tailwind.config.js, as shown below:
// tailwind.config.js
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
... other content
Add the following Tailwind CSS directives to your index.css file in the src directory:
/* ./src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Now that you have completed the Tailwind CSS configuration, start the development server. You'll notice that the default styles from Tailwind's utility classes will be applied to the text. Refer to the official Tailwind CSS documentation for more details on class names and advanced usage.
Before we begin, make sure that you're aligning with the following folder structure:
Here's a summary of the folder structure:
-
./src/components
: This directory contains our main components. Inside this directory, we have:Navbar/
: Contains different components of our Navbar.Layout.jsx
: Defines the basic layout for our application that remains constant across all pages, such as the Navbar.Pages.jsx
: Contains different page components of our application.
-
index.css
: Contains directives for Tailwind CSS. -
font.css
: Imports custom fonts. -
main.jsx
andApp.jsx
: These are the entry point files for our project.
I hope you've understood the folder structure and made it so far successfully. Let's quickly write the code for each of these files one by one.
Setting up Router
Let's begin by setting up react-router-dom in the App.jsx file. Without setting up the router first, you may encounter errors when you're using the Link component. You may check out the official documentation of react-router-dom to learn more about it.
// ./src/App.jsx
import "./font.css";
import Layout from "./components/Layout";
import {
Home, About, Solutions, Products, Blog, Contact, FrontEndDev,
BackEndDev, FullStackDev, AppDevelopment, CloudService, AIandML,
ThreatIntelligence, NetworkSecurity, Compliance, Regulations
} from "./components/Pages";
import {
createBrowserRouter,
RouterProvider
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
children: [
{
path: "",
element: <Home />
},
{
path: "/about",
element: <About />
},
{
path: "/solutions",
element: <Solutions />
},
{
path: "/products",
element: <Products />
},
{
path: "/blog",
element: <Blog />
},
{
path: "/contact",
element: <Contact />
},
{
path: "/services",
children: [
{
path: "web-development",
children: [
{
path: "front-end",
element: <FrontEndDev />
},
{
path: "back-end",
element: <BackEndDev />
},
{
path: "full-stack",
element: <FullStackDev />
}
]
},
{
path: "app-development",
element: <AppDevelopment />
},
{
path: "cloud-services",
element: <CloudService />
},
{
path: "ai-and-ml",
element: <AIandML />
},
{
path: "cybersecurity",
children: [
{
path: "threat-intelligence",
element: <ThreatIntelligence />
},
{
path: "network-security",
element: <NetworkSecurity />
},
{
path: "compliance",
element: <Compliance />
},
{
path: "regulations",
element: <Regulations />
},
]
}
]
}
]
}
])
function App() {
return (
<RouterProvider router={router} />
)
}
export default App
The App.jsx file creates a router object using react-router-dom and passes it to the RouterProvider component. This is how you can define multilevel nested client-side routing. Layout.jsx creates the layout that will remain the same across all pages. You can also define this layout at the nested level. Additionally, there are several components importing from the Pages.jsx, which will be rendered according to the routes defined in App.jsx. The App component is called in the main.jsx. Here's the code for the font.css, Layout.jsx, and Pages.jsx files, respectively.
/* ./src/font.css */
@import url('https://fonts.googleapis.com/css2?family=Karla:ital,wght@0,200..800;1,200..800&display=swap');
body {
font-family: "Karla", sans-serif;
font-optical-sizing: auto;
}
We've imported Karla fonts from Google Fonts and applied them to the body of our application.
// ./src/components/Layout.jsx
import Navbar from "./Navbar/Navbar";
import { Outlet } from "react-router-dom";
function Layout() {
return (
<>
<Navbar />
<div className="
bg-[url('/background-image-sm.jpeg')]
sm:bg-[url('/background-image-md.jpeg')]
lg:bg-[url('/background-image-lg.jpeg')]
bg-cover bg-center
h-screen"
>
<div className="h-full w-full bg-white bg-opacity-20">
<Outlet />
</div>
</div>
</>
)
}
export default Layout;
In our Layout.jsx file, we created a layout using the Outlet component from react-router-dom. This component is rendered at the root level in App.jsx file, so its content will remain across the entire application. Therefore, we invoke the Navbar component here in layout. Additionally, you'll see the background images covering the entire screen. You can download images from the GitHub repository (./public directory).
// ./src/components/Pages.jsx
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
const PageComponent = ({ path, title }) => {
return (
<div className="flex gap-x-3 items-center bg-white absolute bottom-5 left-[50%] -translate-x-[50%] p-5 rounded-md">
<span className="lg:text-lg md:text-md sm:text-sm font-semibold">{path}</span>
<ArrowForwardIosIcon sx={{ fontSize: 16 }} />
<h1 className="lg:text-lg md:text-md sm:text-sm font-semibold">{title}</h1>
</div>
);
};
export const Home = () => <PageComponent path="/" title="Home Page" />;
export const About = () => <PageComponent path="/about" title="About Page" />;
export const Solutions = () => <PageComponent path="/solutions" title="Solutions Page" />;
export const Products = () => <PageComponent path="/products" title="Products Page" />;
export const Blog = () => <PageComponent path="/blog" title="Blog's Page" />;
export const Contact = () => <PageComponent path="/contact" title="Contact Page" />;
export const FrontEndDev = () => <PageComponent path="/services/web-devlopment/front-end" title="Front End Development Page" />;
export const BackEndDev = () => <PageComponent path="/services/web-devlopment/back-end" title="Back End Development Page" />;
export const FullStackDev = () => <PageComponent path="/services/web-devlopment/full-stack" title="Full Stack Development Page" />;
export const AppDevelopment = () => <PageComponent path="/services/app-development" title="App Development Page" />;
export const CloudService = () => <PageComponent path="/services/cloud-services" title="Cloud Services Page" />;
export const AIandML = () => <PageComponent path="/services/ai-and-ml" title="AI & Machine Learning Page" />;
export const ThreatIntelligence = () => <PageComponent path="/services/cybersecurity/threat-intelligence" title="Threat Intelligence Page" />;
export const NetworkSecurity = () => <PageComponent path="/services/cybersecurity/network-security" title="Network Security Page" />;
export const Compliance = () => <PageComponent path="/services/cybersecurity/compliance" title="Compliance Page" />;
export const Regulations = () => <PageComponent path="/services/cybersecurity/regulations" title="Regulations Page" />;
This Pages.jsx file contains components for different pages. In a larger project, you'll obviously create separate files or folders for pages. For this tutorial, pages are created as part of making a Navbar functional. Now that we've set up client-side routing, let's write code for Navbar components.
Components of Navbar
We'll create four components of Navbar, which include the Navbar.jsx, the Dropdown.jsx for dropdown menu, and two components for the second-level dropdown, which include WebDevDD.jsx and CybersecurityDD.jsx.
// ./src/components/Navbar.jsx
import { useState, useEffect, useRef } from "react";
import DropDown from "./Dropdown";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Link } from "react-router-dom";
import { IconButton } from "@mui/material";
import MenuIcon from '@mui/icons-material/Menu';
import CloseIcon from '@mui/icons-material/Close';
const MenuItem = ({ to, label, onClick }) => (
<li className="hover:text-[#98ABEE] transition duration-300">
<Link to={to} onClick={onClick}>{label}</Link>
</li>
);
const MobileMenuItem = ({ to, label, onClick }) => (
<li>
<Link to={to} onClick={onClick} className="py-3 px-5 border-b border-[#272727] w-full inline-block hover:text-[#98ABEE] hover:bg-[#303030] transition duration-300">{label}</Link>
</li>
);
function Navbar() {
const [open, setOpen] = useState(false);
const [openMenu, setOpenMenu] = useState(false);
const navbarRef = useRef(null);
const [verticalMenubarHeight, setVerticalMenubarHeight] = useState('0px');
useEffect(() => {
setOpen(false);
}, [openMenu]);
useEffect(() => {
const handleResize = () => {
if (navbarRef.current) {
const navbarHeight = navbarRef.current.offsetHeight;
setVerticalMenubarHeight(`${window.innerHeight - navbarHeight}px`);
}
};
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
const style = {
height: verticalMenubarHeight,
transform: openMenu ? 'translateX(0)' : 'translateX(-100%)',
transition: 'transform 0.3s ease-in-out',
};
return (
<header>
<div ref={navbarRef} className="bg-[#202020] text-white md:px-5 px-3 relative z-50">
<div className="flex items-center">
{/* Logo */}
<div className="md:py-4 py-3">
<div className="flex gap-x-3 items-center">
<img className="h-10" src="/logo.png" alt="Tech Sphere Logo" />
<h1 className="font-semibold text-xl">Tech Sphere</h1>
</div>
</div>
{/* MenuItem */}
<div className="flex-grow">
<div className="flex justify-end gap-x-5 xl:gap-x-10 items-center">
{/* Desktop Menu */}
<ul className="hidden lg:flex items-center gap-x-5 xl:gap-x-10">
<MenuItem to="/" label="Home" />
<MenuItem to="/about" label="About Us" />
<li className="py-6 flex items-center gap-x-2 cursor-pointer hover:text-[#98ABEE] transition duration-300" onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
Services <span className={`transition duration-300 inline-block ${open ? "rotate-180" : "rotate-0"}`}><ExpandMoreIcon /></span>
<DropDown setOpen={setOpen} open={open} />
</li>
<MenuItem to="/solutions" label="Solutions" />
<MenuItem to="/products" label="Products" />
<MenuItem to="/blog" label="Blog" />
<MenuItem to="/contact" label="Contact Us" />
</ul>
{/* Mobile Menu Toggle */}
<div className="block lg:hidden">
<IconButton onClick={() => setOpenMenu(!openMenu)}>
{openMenu ? <CloseIcon sx={{ color: 'white' }} /> : <MenuIcon sx={{ color: 'white' }} />}
</IconButton>
</div>
</div>
{/* Mobile Menu */}
<ul style={style} className="w-full transition-all duration-500 overflow-auto border-t-2 px-0 border-[#303030] absolute top-[100%] left-0 bg-[#202020] flex flex-col lg:hidden">
<MobileMenuItem to="/" label="Home" onClick={() => setOpenMenu(false)} />
<MobileMenuItem to="/about" label="About Us" onClick={() => setOpenMenu(false)} />
<li>
<span className={`${open ? "bg-[#272727]" : ""} cursor-pointer hover:text-[#98ABEE] transition-all duration-300 py-3 px-5 border-b border-[#272727] flex items-center justify-between hover:bg-[#303030]`} onClick={() => setOpen(!open)}>
Services
<span className={`transition duration-500 inline-block ${open ? "rotate-180" : "rotate-0"}`}><ExpandMoreIcon /></span>
</span>
<DropDown open={open} setOpenMenu={setOpenMenu} />
</li>
<MobileMenuItem to="/solutions" label="Solutions" onClick={() => setOpenMenu(false)} />
<MobileMenuItem to="/products" label="Products" onClick={() => setOpenMenu(false)} />
<MobileMenuItem to="/blog" label="Blog" onClick={() => setOpenMenu(false)} />
<MobileMenuItem to="/contact" label="Contact Us" onClick={() => setOpenMenu(false)} />
</ul>
</div>
</div>
</div>
</header>
);
}
export default Navbar;
The Navbar component defines the responsive navigation bar with separate layouts for both desktop and mobile screens. You can hide or show menus for desktop and mobile using Tailwind's responsive breakpoints, such as lg, md, sm, etc. Additionally, we've created two subcomponents, MenuItem and MobileMenuItem, to avoid repeating the same code.
The Navbar dynamically adjusts its height on mobile devices to fill the screen below the header, calculated using the component's offsetHeight and window height. It also includes a DropDown component for nested menus under the "Services" item, which changes based on user interactions such as hovering on desktop or clicking on mobile. Now, let's create the DropDown component.
// ./src/components/Dropdown.jsx
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
import WebDropDown from "./WebDevDD";
import CybersecurityDropDown from "./CybersecurityDD";
import ComputerIcon from '@mui/icons-material/Computer';
import AppSettingsAltIcon from '@mui/icons-material/AppSettingsAlt';
import CloudIcon from '@mui/icons-material/Cloud';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import SecurityIcon from '@mui/icons-material/Security';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import AddIcon from '@mui/icons-material/Add';
function DropDown({ open, setOpenMenu, setOpen }) {
const [openWebDropdown, setOpenWebDropdown] = useState(false);
const [openCybersecurityDropdown, setOpenCybersecurityDropdown] = useState(false);
useEffect(() => {
setOpenWebDropdown(false);
setOpenCybersecurityDropdown(false);
}, [open]);
const handleWebDropDown = () => {
setOpenWebDropdown(!openWebDropdown);
setOpenCybersecurityDropdown(false);
};
const handleCybersecurityDropDown = () => {
setOpenCybersecurityDropdown(!openCybersecurityDropdown);
setOpenWebDropdown(false);
};
const dropdownItems = [
{ icon: ComputerIcon, label: "Web Development", dropdown: true, isOpen: openWebDropdown, handleClick: handleWebDropDown, children: <WebDropDown open={openWebDropdown} setOpen={setOpen} setOpenMenu={setOpenMenu} /> },
{ icon: AppSettingsAltIcon, label: "App Development", to: "/services/app-development" },
{ icon: CloudIcon, label: "Cloud Services", to: "/services/cloud-services" },
{ icon: SmartToyIcon, label: "AI & Machine Learning", to: "/services/ai-and-ml" },
{ icon: SecurityIcon, label: "Cybersecurity", dropdown: true, isOpen: openCybersecurityDropdown, handleClick: handleCybersecurityDropDown, children: <CybersecurityDropDown open={openCybersecurityDropdown} setOpen={setOpen} setOpenMenu={setOpenMenu} /> },
];
return (
<>
{/* Desktop DropDown */}
<ul className={`${open ? "opacity-100 visible lg:block hidden" : "opacity-0 invisible hidden"} animate-slideInOut transition-all duration-300 absolute top-[99%] w-60 bg-[#202020] text-white`}>
{dropdownItems.map(({ icon: Icon, label, to, dropdown, isOpen, handleClick, children }) => (
<li key={label}>
{dropdown ? (
<span className="relative flex justify-between items-center py-3 px-5 hover:bg-[#303030]" onMouseEnter={handleClick} onMouseLeave={handleClick}>
<span className="flex gap-x-3 items-center">
<Icon sx={{ fontSize: 20 }} /> {label}
</span>
<span className={`transition duration-300 inline-block ${isOpen ? "-rotate-90" : "rotate-0"}`}><ExpandMoreIcon /></span>
{children}
</span>
) : (
<Link to={to} onClick={() => setOpen(false)} className="flex gap-x-3 items-center py-3 px-5 hover:bg-[#303030]">
<Icon sx={{ fontSize: 20 }} /> {label}
</Link>
)}
</li>
))}
</ul>
{/* Mobile DropDown */}
<ul className={`${open ? "max-h-[1000px]" : "max-h-0"} lg:hidden relative flex overflow-hidden transition-all duration-500 bg-[#272727] w-full flex-col text-white`}>
{dropdownItems.map(({ icon: Icon, label, to, dropdown, isOpen, handleClick, children }) => (
<li key={label}>
{dropdown ? (
<>
<span className={`${isOpen ? "bg-[#303030] hover:bg-[#373737]" : ""} hover:bg-[#303030] cursor-pointer flex justify-between py-3 px-8`} onClick={handleClick}>
<span className="flex gap-x-3 items-center">
<Icon sx={{ fontSize: 20 }} /> {label}
</span>
<span className={`transition duration-500 inline-block ${isOpen ? "-rotate-45" : "rotate-0"}`}><AddIcon sx={{ fontSize: 20 }} /></span>
</span>
{children}
</>
) : (
<Link to={to} onClick={() => setOpenMenu(false)} className="flex gap-x-3 items-center py-3 px-8 hover:bg-[#303030]">
<Icon sx={{ fontSize: 20 }} /> {label}
</Link>
)}
</li>
))}
</ul>
</>
);
}
export default DropDown;
The DropDown component defines the first-level dropdown. Instead of creating MenuItem components within the Navbar, we render an array of dropdown items here to show an alternative approach. Some dropdown items also include sub-dropdown components.
There is a className called animate-slideInOut in the desktop dropdown menu that is used for slide-in/out animation. This is not directly provided by Tailwind, but we've configured it in our tailwind.config.js file as given below.
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
keyframes: {
slideInOut: {
'0%': {
opacity: 0,
visibility: 'hidden',
transform: 'translateY(-20px)'
},
'50%': {
opacity: 0.9,
visibility: 'visible',
transform: 'translateY(0)'
},
'100%': {
opacity: 1,
visibility: 'visible',
transform: 'translateY(0)'
}
}
},
animation: {
slideInOut: 'slideInOut 0.5s ease-in-out forwards'
}
},
},
plugins: [],
}
You can configure various custom styles using this tailwind.config.js file. This is just one example. Now, let's quickly write a code for the sub-dropdown menu components, WebDevDD.jsx and CybersecurityDD.jsx.
// ./src/components/WebDevDD.jsx
import { Link } from "react-router-dom";
const DropDownLink = ({ to, onClick, label }) => (
<li>
<Link to={to} onClick={onClick} className="flex py-3 px-16 lg:px-5 hover:bg-[#373737] lg:hover:bg-[#303030]">
{label}
</Link>
</li>
);
function WebDropDown({ open, setOpen, setOpenMenu }) {
const dropdownLinks = [
{ to: "/services/web-development/front-end", label: "Front End Development" },
{ to: "/services/web-development/back-end", label: "Back End Development" },
{ to: "/services/web-development/full-stack", label: "Full Stack Development" },
];
return (
<>
{/* Desktop DropDown */}
<ul className={`${open ? "opacity-100 translate-x-0 visible" : "opacity-0 -translate-x-4 invisible"} lg:block hidden transition-all duration-300 absolute left-full top-0 border border-[#303030] bg-[#202020] w-56`}>
{dropdownLinks.map(({ to, label }) => (
<DropDownLink key={to} to={to} label={label} onClick={() => setOpen(false)} />
))}
</ul>
{/* Mobile DropDown */}
<ul className={`${open ? "max-h-[500px]" : "max-h-0"} lg:hidden flex flex-col w-full overflow-hidden transition-all duration-500 bg-[#303030]`}>
{dropdownLinks.map(({ to, label }) => (
<DropDownLink key={to} to={to} label={label} onClick={() => setOpenMenu(false)} />
))}
</ul>
</>
);
}
export default WebDropDown;
This component defines a sub-dropdown menu for the "Web Development" menu within the dropdown. In order to render the list of sub-dropdown menus, we've created an array of items and a subcomponent. Additionally, we've written a separate code for desktop and mobile screens to maintain the cleanliness of our code. Here's a code for another sub-dropdown component.
// ./src/components/CybersecurityDD.jsx
import { Link } from "react-router-dom";
const DropDownLink = ({ to, onClick, label }) => (
<li>
<Link to={to} onClick={onClick} className="flex py-3 px-16 lg:px-5 hover:bg-[#373737] lg:hover:bg-[#303030]">
{label}
</Link>
</li>
);
function CybersecurityDropDown({ open, setOpen, setOpenMenu }) {
const dropdownLinks = [
{ to: "/services/cybersecurity/threat-intelligence", label: "Threat Intelligence" },
{ to: "/services/cybersecurity/network-security", label: "Network Security" },
{ to: "/services/cybersecurity/compliance", label: "Compliance" },
{ to: "/services/cybersecurity/regulations", label: "Regulations" },
];
return (
<>
{/* Desktop DropDown */}
<ul className={`${open ? "opacity-100 translate-x-0 visible" : "opacity-0 -translate-x-4 invisible"} lg:block hidden transition-all duration-300 absolute left-full top-0 border border-[#303030] bg-[#202020] w-56`}>
{dropdownLinks.map(({ to, label }) => (
<DropDownLink key={to} to={to} label={label} onClick={() => setOpen(false)} />
))}
</ul>
{/* Mobile DropDown */}
<ul className={`${open ? "max-h-[500px]" : "max-h-0"} lg:hidden block w-full overflow-hidden transition-all duration-500 bg-[#303030]`}>
{dropdownLinks.map(({ to, label }) => (
<DropDownLink key={to} to={to} label={label} onClick={() => setOpenMenu(false)} />
))}
</ul>
</>
)
}
export default CybersecurityDropDown;
This component defines a sub-dropdown menu for the "Cybersecurity" menu within the dropdown. It is pretty similar to our previous component. Alright then, that's it, and our Navbar app is ready. Save all these files and run the following command to start the server:
npm run dev
See the clean and user-friendly UI in action. Here's a GIF of our responsive Navbar!
I hope you've successfully created a Navbar. From now on, you won't find it difficult to create a responsive Navbar with a multilevel dropdown menu. If you've missed something from the code, checkout the complete source code on GitHub.
Takeaway
When starting a web project, creating a complex navbar feels difficult, but when you break down the entire navbar into various re-usable components, it becomes quite easy. Additionally, with the help of Tailwind CSS, it's easier to style it and make it responsive.
Moreover, you can make further enhancements to the codebase and make it dynamic in such a way that you can easily integrate it into your future projects. I hope you've found this tutorial helpful so far. If you've got some ideas for more such tutorials on building different components of a project, feel free to share them in the comments.