apps/docs/content/docs/react/migration/(components)/navbar.mdx
v2: <Navbar> and subcomponents (NavbarBrand, NavbarContent, NavbarItem, NavbarMenu, NavbarMenuItem, NavbarMenuToggle)
v3: Manual composition using native HTML elements
| 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 |
shouldHideOnScroll - Requires custom scroll detectionisBlurred - Use Tailwind backdrop-blur utilitiesisBordered - Use Tailwind border classesposition variants - Use Tailwind sticky, fixed classesmaxWidth variants - Use Tailwind max-w-* classes<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx import { Navbar, NavbarBrand, NavbarContent, NavbarItem, Link, Button } from "@heroui/react"; <Navbar> <NavbarBrand> <Logo /> <p className="font-bold">ACME</p> </NavbarBrand> <NavbarContent> <NavbarItem><Link href="#">Features</Link></NavbarItem> <NavbarItem><Link href="#">Pricing</Link></NavbarItem> </NavbarContent> </Navbar> <Navbar> <NavbarBrand>Logo</NavbarBrand> <NavbarContent justify="end"> <NavbarItem><Button>Sign Up</Button></NavbarItem> </NavbarContent> </Navbar>
</Tab>
<Tab value="v3">
tsx import { Link, 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 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> </ul> </header> </nav> <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>
</Tab>
</Tabs>
<Tabs items={["v2", "v3"]}> <Tab value="v2"> ```tsx 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>
);
}
```
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>
);
}
```
<Tabs items={["v2", "v3"]}> <Tab value="v2"> ```tsx 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>
);
}
```
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>
);
}
```
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>
</>
}
/>
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"
)}
>
</nav>
);
}
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]);
Navbar, NavbarBrand, NavbarContent, NavbarItem, NavbarMenu, NavbarMenuItem, NavbarMenuToggle)@heroui/react<Navbar> with <nav> element<header>, <ul>, <li>)useState<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>
import { Dropdown, Button, Label } from "@heroui/react";
<ul className="flex items-center gap-4">
<li>
<Dropdown>
<Button variant="ghost">Features</Button>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="feature1" textValue="Feature 1">
<Label>Feature 1</Label>
</Dropdown.Item>
<Dropdown.Item id="feature2" textValue="Feature 2">
<Label>Feature 2</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
</li>
</ul>