Navbar
Migration guide for Navbar from HeroUI v2 to v3
The Navbar component has been removed in HeroUI v3. Build navigation bars manually using native HTML elements and Tailwind CSS classes. This guide focuses on common patterns and simplifies complex features.
Overview
The Navbar component was a complex navigation component with multiple subcomponents, mobile menu support, scroll hiding, and animations. In v3, you should build navigation bars manually using semantic HTML (<nav>, <header>, <ul>, <li>) and Tailwind CSS classes.
Key Changes
1. Component Removal
v2: <Navbar> and subcomponents (NavbarBrand, NavbarContent, NavbarItem, NavbarMenu, NavbarMenuItem, NavbarMenuToggle)
v3: Manual composition using native HTML elements
2. Subcomponents Mapping
| v2 Component | v3 Equivalent | Notes |
|---|---|---|
Navbar | <nav> element | Main container |
NavbarBrand | <div> or <a> | Logo/brand area |
NavbarContent | <ul> element | Navigation list |
NavbarItem | <li> element | Navigation item |
NavbarMenu | Mobile menu overlay | Custom implementation |
NavbarMenuItem | <li> in mobile menu | Mobile menu item |
NavbarMenuToggle | <button> | Mobile menu toggle |
3. Features Removed
shouldHideOnScroll- Requires custom scroll detectionisBlurred- Use Tailwindbackdrop-blurutilitiespositionvariants - Use Tailwindsticky,fixedclassesmaxWidthvariants - Use Tailwindmax-w-*classes- Mobile menu animations - Requires custom implementation
- Scroll blocking - Requires custom implementation
Migration Examples
Basic Navbar
import { Navbar, NavbarBrand, NavbarContent, NavbarItem, Link } from "@heroui/react";
export default function App() {
return (
<Navbar>
<NavbarBrand>
<Logo />
<p className="font-bold">ACME</p>
</NavbarBrand>
<NavbarContent>
<NavbarItem>
<Link href="#">Features</Link>
</NavbarItem>
<NavbarItem>
<Link href="#">Pricing</Link>
</NavbarItem>
<NavbarItem>
<Link href="#">About</Link>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}import { Link } from "@heroui/react";
export default function App() {
return (
<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg">
<header className="flex h-16 items-center justify-between px-6">
<div className="flex items-center gap-3">
<Logo />
<p className="font-bold">ACME</p>
</div>
<ul className="flex items-center gap-4">
<li>
<Link href="#">Features</Link>
</li>
<li>
<Link href="#">Pricing</Link>
</li>
<li>
<Link href="#">About</Link>
</li>
</ul>
</header>
</nav>
);
}With Right-Aligned Content
<Navbar>
<NavbarBrand>Logo</NavbarBrand>
<NavbarContent justify="end">
<NavbarItem>
<Button>Sign Up</Button>
</NavbarItem>
</NavbarContent>
</Navbar>import { Button } from "@heroui/react";
<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg">
<header className="flex h-16 items-center justify-between px-6">
<div>Logo</div>
<ul className="flex items-center gap-4">
<li>
<Button>Sign Up</Button>
</li>
</ul>
</header>
</nav>With Mobile Menu (Simplified)
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
NavbarMenu,
NavbarMenuItem,
NavbarMenuToggle,
} from "@heroui/react";
function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<Navbar onMenuOpenChange={setIsMenuOpen}>
<NavbarContent>
<NavbarMenuToggle className="sm:hidden" />
<NavbarBrand>Logo</NavbarBrand>
</NavbarContent>
<NavbarContent className="hidden md:flex">
<NavbarItem>Features</NavbarItem>
<NavbarItem>Pricing</NavbarItem>
</NavbarContent>
<NavbarMenu>
<NavbarMenuItem>Features</NavbarMenuItem>
<NavbarMenuItem>Pricing</NavbarMenuItem>
</NavbarMenu>
</Navbar>
);
}import { useState } from "react";
import { Link, Button } from "@heroui/react";
function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg">
<header className="flex h-16 items-center justify-between px-6">
<div className="flex items-center gap-4">
<button
className="md:hidden"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label="Toggle menu"
>
<span className="sr-only">Menu</span>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
<div>Logo</div>
</div>
<ul className="hidden items-center gap-4 md:flex">
<li>
<Link href="#">Features</Link>
</li>
<li>
<Link href="#">Pricing</Link>
</li>
</ul>
</header>
{isMenuOpen && (
<div className="border-t border-separator md:hidden">
<ul className="flex flex-col gap-2 p-4">
<li>
<Link href="#" className="block py-2">
Features
</Link>
</li>
<li>
<Link href="#" className="block py-2">
Pricing
</Link>
</li>
</ul>
</div>
)}
</nav>
);
}With Active State
<NavbarItem isActive>
<Link href="#">Dashboard</Link>
</NavbarItem><li>
<Link
href="#"
className="font-medium text-accent"
aria-current="page"
>
Dashboard
</Link>
</li>With Max Width Container
<Navbar maxWidth="lg">
{/* content */}
</Navbar><nav className="sticky top-0 z-40 w-full">
<header className="mx-auto flex h-16 max-w-5xl items-center justify-between px-6">
{/* content */}
</header>
</nav>Complete Example
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
NavbarMenu,
NavbarMenuItem,
NavbarMenuToggle,
Link,
Button,
} from "@heroui/react";
export default function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<Navbar position="sticky" maxWidth="lg" onMenuOpenChange={setIsMenuOpen}>
<NavbarContent>
<NavbarMenuToggle className="sm:hidden" />
<NavbarBrand>
<Logo />
<p className="font-bold">ACME</p>
</NavbarBrand>
</NavbarContent>
<NavbarContent className="hidden md:flex">
<NavbarItem>
<Link href="#">Features</Link>
</NavbarItem>
<NavbarItem isActive>
<Link href="#">Dashboard</Link>
</NavbarItem>
<NavbarItem>
<Link href="#">Pricing</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem>
<Link href="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button>Sign Up</Button>
</NavbarItem>
</NavbarContent>
<NavbarMenu>
<NavbarMenuItem>
<Link href="#">Features</Link>
</NavbarMenuItem>
<NavbarMenuItem>
<Link href="#">Dashboard</Link>
</NavbarMenuItem>
<NavbarMenuItem>
<Link href="#">Pricing</Link>
</NavbarMenuItem>
</NavbarMenu>
</Navbar>
);
}import { useState } from "react";
import { Link, Button } from "@heroui/react";
export default function App() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg">
<header className="mx-auto flex h-16 max-w-5xl items-center justify-between px-6">
<div className="flex items-center gap-4">
<button
className="md:hidden"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label="Toggle menu"
aria-expanded={isMenuOpen}
>
<span className="sr-only">Menu</span>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
<div className="flex items-center gap-3">
<Logo />
<p className="font-bold">ACME</p>
</div>
</div>
<ul className="hidden items-center gap-4 md:flex">
<li>
<Link href="#">Features</Link>
</li>
<li>
<Link href="#" className="font-medium text-accent" aria-current="page">
Dashboard
</Link>
</li>
<li>
<Link href="#">Pricing</Link>
</li>
</ul>
<div className="hidden items-center gap-4 md:flex">
<Link href="#">Login</Link>
<Button>Sign Up</Button>
</div>
</header>
{isMenuOpen && (
<div className="border-t border-separator md:hidden">
<ul className="flex flex-col gap-2 p-4">
<li>
<Link href="#" className="block py-2">
Features
</Link>
</li>
<li>
<Link href="#" className="block py-2 font-medium text-accent">
Dashboard
</Link>
</li>
<li>
<Link href="#" className="block py-2">
Pricing
</Link>
</li>
<li className="mt-4 flex flex-col gap-2 border-t border-separator pt-4">
<Link href="#" className="block py-2">
Login
</Link>
<Button className="w-full">Sign Up</Button>
</li>
</ul>
</div>
)}
</nav>
);
}Creating a Reusable Navbar Component (Recommended)
Since navbars are commonly needed, here's a simplified reusable component:
import { useState, ReactNode } from "react";
import { Link, Button } from "@heroui/react";
import { cn } from "@/lib/utils"; // or your cn utility
interface NavbarItem {
label: string;
href: string;
isActive?: boolean;
}
interface NavbarProps {
brand: ReactNode;
items: NavbarItem[];
rightContent?: ReactNode;
className?: string;
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
position?: "static" | "sticky" | "fixed";
}
const maxWidthClasses = {
sm: "max-w-[640px]",
md: "max-w-[768px]",
lg: "max-w-[1024px]",
xl: "max-w-[1280px]",
"2xl": "max-w-[1536px]",
full: "max-w-full",
};
export function Navbar({
brand,
items,
rightContent,
className,
maxWidth = "lg",
position = "sticky",
}: NavbarProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<nav
className={cn(
"z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg",
position === "sticky" && "sticky top-0",
position === "fixed" && "fixed top-0",
className
)}
>
<header
className={cn(
"flex h-16 items-center justify-between px-6",
maxWidth !== "full" && maxWidthClasses[maxWidth],
"mx-auto"
)}
>
<div className="flex items-center gap-4">
<button
className="md:hidden"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label="Toggle menu"
aria-expanded={isMenuOpen}
>
<span className="sr-only">Menu</span>
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{isMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
{brand}
</div>
<ul className="hidden items-center gap-4 md:flex">
{items.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className={cn(item.isActive && "font-medium text-accent")}
aria-current={item.isActive ? "page" : undefined}
>
{item.label}
</Link>
</li>
))}
</ul>
{rightContent && <div className="hidden items-center gap-4 md:flex">{rightContent}</div>}
</header>
{isMenuOpen && (
<div className="border-t border-separator md:hidden">
<ul className="flex flex-col gap-2 p-4">
{items.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className={cn(
"block py-2",
item.isActive && "font-medium text-accent"
)}
>
{item.label}
</Link>
</li>
))}
{rightContent && (
<li className="mt-4 flex flex-col gap-2 border-t border-separator pt-4">
{rightContent}
</li>
)}
</ul>
</div>
)}
</nav>
);
}
// Usage
<Navbar
brand={
<>
<Logo />
<p className="font-bold">ACME</p>
</>
}
items={[
{ label: "Features", href: "#features" },
{ label: "Dashboard", href: "#dashboard", isActive: true },
{ label: "Pricing", href: "#pricing" },
]}
rightContent={
<>
<Link href="#login">Login</Link>
<Button>Sign Up</Button>
</>
}
/>Advanced Features (Custom Implementation Required)
Hide on Scroll
The shouldHideOnScroll feature requires custom scroll detection:
import { useState, useEffect } from "react";
function useScrollDirection() {
const [isHidden, setIsHidden] = useState(false);
const [lastScrollY, setLastScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
setIsHidden(currentScrollY > lastScrollY && currentScrollY > 64);
setLastScrollY(currentScrollY);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [lastScrollY]);
return isHidden;
}
function NavbarWithHideOnScroll() {
const isHidden = useScrollDirection();
return (
<nav
className={cn(
"sticky top-0 z-40 w-full transition-transform duration-300",
isHidden && "-translate-y-full"
)}
>
{/* navbar content */}
</nav>
);
}Scroll Blocking
For blocking scroll when mobile menu is open:
useEffect(() => {
if (isMenuOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => {
document.body.style.overflow = "";
};
}, [isMenuOpen]);Breaking Changes Summary
- Component Removed: All Navbar components removed (
Navbar,NavbarBrand,NavbarContent,NavbarItem,NavbarMenu,NavbarMenuItem,NavbarMenuToggle) - Import Change: Remove all Navbar imports from
@heroui/react - Manual Composition: Build navbars using native HTML elements
- Mobile Menu: Implement mobile menu manually with state management
- Styling: Apply Tailwind CSS classes directly
- Advanced Features: Hide on scroll, animations require custom implementation
Migration Steps
- Remove Imports: Remove all Navbar-related imports
- Replace Structure: Replace
<Navbar>with<nav>element - Replace Subcomponents: Replace subcomponents with semantic HTML (
<header>,<ul>,<li>) - Add Mobile Menu: Implement mobile menu toggle and menu manually
- Apply Styling: Use Tailwind CSS classes for layout and styling
- Handle State: Manage mobile menu state with React
useState - Optional: Create reusable Navbar component for your application
Tips for Migration
- Start Simple: Begin with a basic navbar structure, then add features
- Use Semantic HTML: Use
<nav>,<header>,<ul>,<li>for accessibility - Mobile-First: Design mobile menu first, then add desktop navigation
- Accessibility: Include
aria-label,aria-expandedfor mobile toggle - Responsive: Use Tailwind responsive utilities (
md:flex,md:hidden) - Reusable Component: Create a reusable component if you have multiple navbars
- Keep It Simple: Avoid over-engineering - start with basic functionality
Common Patterns
Simple Navigation
<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background">
<header className="flex h-16 items-center justify-between px-6">
<div>Logo</div>
<ul className="flex items-center gap-4">
<li><Link href="#">Home</Link></li>
<li><Link href="#">About</Link></li>
<li><Link href="#">Contact</Link></li>
</ul>
</header>
</nav>With Dropdown (using v3 Dropdown)
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from "@heroui/react";
<ul className="flex items-center gap-4">
<li>
<Dropdown>
<DropdownTrigger>
<Button variant="ghost">Features</Button>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem>Feature 1</DropdownItem>
<DropdownItem>Feature 2</DropdownItem>
</DropdownMenu>
</Dropdown>
</li>
</ul>Need Help?
For styling guidance:
- See the Styling documentation
- Check Tailwind CSS documentation for layout utilities
For mobile menu patterns:
For community support: