Use Navigation Menu for navigation between pages. Put direct destinations in NavigationMenuLink and grouped destinations inside a trigger/content pair. Use Menubar for application commands.
Live preview
"use client";
import * as React from "react";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
const components: { title: string; href: string; description: string }[] = [
{
title: "Alert Dialog",
href: "/docs/alert-dialog",
description:
"A modal dialog that interrupts the user with important content and expects a response.",
},
{
title: "Hover Card",
href: "/docs/hover-card",
description: "For sighted users to preview content available behind a link.",
},
{
title: "Progress",
href: "/docs/progress",
description:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
},
{
title: "Scroll Area",
href: "/docs/scroll-area",
description: "Provides a consistent scrollable viewport for long content.",
},
{
title: "Tabs",
href: "/docs/tabs",
description:
"A set of layered sections of content, known as tab panels, that are displayed one at a time.",
},
{
title: "Tooltip",
href: "/docs/tooltip",
description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
},
];
export default function NavigationMenuDemo() {
return (
<NavigationMenu className="z-5 ">
<NavigationMenuList>
<NavigationMenuItem className="sm:block hidden">
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[min(500px,calc(100vw-2rem))] gap-3 p-2 lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink
className="flex h-full w-full select-none flex-col justify-end rounded-base p-6 no-underline outline-hidden"
href="/docs/"
>
<div className="mb-2 mt-4 text-lg font-heading">neobrutal-ui</div>
<p className="text-sm font-base leading-tight">
Editable React components built with Base UI and Tailwind CSS.
</p>
</NavigationMenuLink>
</li>
<ListItem href="/docs/" title="Introduction">
Learn the project model and how the registry fits into your app.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
Install components with the shadcn CLI or copy them manually.
</ListItem>
<ListItem href="/docs/design-tokens" title="Design tokens">
Customize color, type, borders, radius, and hard shadows.
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[min(400px,calc(100vw-2rem))] gap-1 p-2 sm:w-[500px] sm:grid-cols-2 sm:gap-3 lg:w-[600px]">
{components.map((component) => (
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink className={navigationMenuTriggerStyle()} href="/docs/">
Documentation
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
);
}
function ListItem({ className, title, children, ...props }: React.ComponentProps<"a">) {
return (
<li>
<NavigationMenuLink
className={cn(
"hover:bg-accent block text-main-foreground select-none space-y-1 rounded-base border-2 border-transparent p-2 leading-none no-underline outline-hidden transition-colors hover:border-border sm:p-3",
className,
)}
{...props}
>
<div className="text-base font-heading leading-none">{title}</div>
<p className="font-base hidden text-sm leading-snug sm:line-clamp-2">{children}</p>
</NavigationMenuLink>
</li>
);
}
ListItem.displayName = "ListItem";
#Installation
Complete the base setup. The CLI resolves dependencies. For manual installation:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/navigation-menu.jsonnpm install @base-ui/react@^1.8.0Create components/ui/navigation-menu.tsx:
"use client";
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu";
import { ChevronDownIcon } from "lucide-react";
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
function NavigationMenu({
children,
className,
...props
}: NavigationMenuPrimitive.Root.Props<string>) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
{...props}
className={(state) =>
cn(
"group/navigation-menu relative z-10 flex max-w-max flex-1 items-center justify-center rounded-base border-2 border-border bg-main p-1 font-heading text-main-foreground",
typeof className === "function" ? className(state) : className,
)
}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({ className, ...props }: NavigationMenuPrimitive.List.Props) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
{...props}
className={(state) =>
cn(
"group flex flex-1 list-none items-center justify-center gap-1 font-heading",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function NavigationMenuItem({ className, ...props }: NavigationMenuPrimitive.Item.Props) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
{...props}
className={(state) =>
cn("relative", typeof className === "function" ? className(state) : className)
}
/>
);
}
const navigationMenuTriggerStyle = cva(
"group/navigation-menu-trigger inline-flex h-10 w-max items-center justify-center rounded-base bg-main px-4 py-2 text-sm font-heading text-main-foreground transition-colors outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-4 focus-visible:outline-main-foreground disabled:pointer-events-none disabled:opacity-50",
);
function NavigationMenuTrigger({
className,
children,
...props
}: NavigationMenuPrimitive.Trigger.Props) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
{...props}
className={(state) =>
cn(
navigationMenuTriggerStyle(),
"group",
typeof className === "function" ? className(state) : className,
)
}
>
{children}
<NavigationMenuPrimitive.Icon
data-slot="navigation-menu-icon"
className="relative top-px ml-2 transition duration-200 group-data-popup-open/navigation-menu-trigger:rotate-180"
>
<ChevronDownIcon className="size-4" aria-hidden="true" />
</NavigationMenuPrimitive.Icon>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({ className, ...props }: NavigationMenuPrimitive.Content.Props) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
{...props}
className={(state) =>
cn(
"h-full w-auto p-2 pr-2.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[activation-direction=down]:data-ending-style:-translate-y-1/2 data-[activation-direction=down]:data-starting-style:translate-y-1/2 data-[activation-direction=left]:data-ending-style:translate-x-1/2 data-[activation-direction=left]:data-starting-style:-translate-x-1/2 data-[activation-direction=right]:data-ending-style:-translate-x-1/2 data-[activation-direction=right]:data-starting-style:translate-x-1/2 data-[activation-direction=up]:data-ending-style:translate-y-1/2 data-[activation-direction=up]:data-starting-style:-translate-y-1/2 data-ending-style:opacity-0 data-starting-style:opacity-0",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function NavigationMenuViewport({ className, ...props }: NavigationMenuPrimitive.Viewport.Props) {
return (
<NavigationMenuPrimitive.Portal>
<NavigationMenuPrimitive.Positioner
side="bottom"
sideOffset={8}
align="start"
className="isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width)"
>
<NavigationMenuPrimitive.Popup
data-slot="navigation-menu-popup"
className="relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) overflow-hidden rounded-base bg-main text-main-foreground shadow-[inset_0_0_0_2px_var(--border)] transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0"
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
{...props}
className={(state) =>
cn(
"relative h-full w-full overflow-hidden",
typeof className === "function" ? className(state) : className,
)
}
/>
</NavigationMenuPrimitive.Popup>
</NavigationMenuPrimitive.Positioner>
</NavigationMenuPrimitive.Portal>
);
}
function NavigationMenuLink({ className, ...props }: NavigationMenuPrimitive.Link.Props) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
tabIndex={0}
{...props}
className={(state) =>
cn(
"block space-y-1 rounded-base p-2 leading-none no-underline transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-4 focus-visible:outline-main-foreground [&_svg:not([class*='size-'])]:size-4",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuViewport,
};
#Usage
import {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuLink,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
export default function SiteNavigation() {
return (
<NavigationMenu aria-label="Main navigation">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink href="/docs" className={navigationMenuTriggerStyle()}>
Documentation
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="/templates" className={navigationMenuTriggerStyle()}>
Templates
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
NavigationMenu.value / onValueChange | Controlled expanded item | Uncontrolled |
NavigationMenu.defaultValue | Initially expanded item | null |
NavigationMenu.delay / closeDelay | Hover open and close delay | Base UI defaults |
NavigationMenu.orientation | Trigger keyboard navigation | horizontal |
NavigationMenuItem.value | Stable grouped-item identifier when controlling state | Optional |
NavigationMenuLink.href / render | Native destination or router-link composition | Anchor |
NavigationMenuLink.active | Current destination styling and semantics | false |
#Accessibility
Use real links and meaningful text. A trigger opens content rather than navigating. Preserve link props and refs when composing a router link. Mark the current destination and test keyboard and touch interactions. Provide a compact mobile layout rather than squeezing a wide desktop menu.