Back to Heroui

Navbar

apps/docs/content/docs/react/migration/(components)/navbar.mdx

3.0.318.3 KB
Original Source
<Callout type="warning"> 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. </Callout>

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 Componentv3 EquivalentNotes
Navbar<nav> elementMain container
NavbarBrand<div> or <a>Logo/brand area
NavbarContent<ul> elementNavigation list
NavbarItem<li> elementNavigation item
NavbarMenuMobile menu overlayCustom implementation
NavbarMenuItem<li> in mobile menuMobile menu item
NavbarMenuToggle<button>Mobile menu toggle

3. Features Removed

  • shouldHideOnScroll - Requires custom scroll detection
  • isBlurred - Use Tailwind backdrop-blur utilities
  • isBordered - Use Tailwind border classes
  • position variants - Use Tailwind sticky, fixed classes
  • maxWidth variants - Use Tailwind max-w-* classes
  • Mobile menu animations - Requires custom implementation
  • Scroll blocking - Requires custom implementation (see "Scroll Blocking" below)

Migration Examples

Basic Navbar

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

With Mobile Menu (Simplified)

<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>
  );
}
```
</Tab> <Tab value="v3"> ```tsx 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>
  );
}
```
</Tab> </Tabs>

Complete Example

<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>
  );
}
```
</Tab> <Tab value="v3"> ```tsx 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>
  );
}
```
</Tab> </Tabs>

Since navbars are commonly needed, here's a simplified reusable component:

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

tsx
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>
  );
}

Scroll Blocking

For blocking scroll when mobile menu is open:

tsx
useEffect(() => {
  if (isMenuOpen) {
    document.body.style.overflow = "hidden";
  } else {
    document.body.style.overflow = "";
  }
  return () => {
    document.body.style.overflow = "";
  };
}, [isMenuOpen]);

Summary

  1. Component Removed: All Navbar components removed (Navbar, NavbarBrand, NavbarContent, NavbarItem, NavbarMenu, NavbarMenuItem, NavbarMenuToggle)
  2. Import Change: Remove all Navbar imports from @heroui/react
  3. Manual Composition: Build navbars using native HTML elements
  4. Mobile Menu: Implement mobile menu manually with state management
  5. Styling: Apply Tailwind CSS classes directly
  6. Advanced Features: Hide on scroll, animations require custom implementation

Migration Steps

  1. Remove Imports: Remove all Navbar-related imports
  2. Replace Structure: Replace <Navbar> with <nav> element
  3. Replace Subcomponents: Replace subcomponents with semantic HTML (<header>, <ul>, <li>)
  4. Add Mobile Menu: Implement mobile menu toggle and menu manually
  5. Apply Styling: Use Tailwind CSS classes for layout and styling
  6. Handle State: Manage mobile menu state with React useState
  7. Optional: Create reusable Navbar component for your application

Common Patterns

Simple Navigation

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

tsx
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>