27.5k

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

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

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

  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

Tips for Migration

  1. Start Simple: Begin with a basic navbar structure, then add features
  2. Use Semantic HTML: Use <nav>, <header>, <ul>, <li> for accessibility
  3. Mobile-First: Design mobile menu first, then add desktop navigation
  4. Accessibility: Include aria-label, aria-expanded for mobile toggle
  5. Responsive: Use Tailwind responsive utilities (md:flex, md:hidden)
  6. Reusable Component: Create a reusable component if you have multiple navbars
  7. 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:

For mobile menu patterns:

For community support: