Navigation Menu
Website top navigation on the APG disclosure pattern: plain links and panels of rich links. Hover opens after a beat, then sliding along the bar switches panels at once; every panel shares one viewport that springs to the next panel's size and position while the content slides in from the side you moved toward. Click, Enter, ↓, ← → and Esc work; closed panels keep their links in the HTML for crawlers.
npx @kznlabs/labsui add navigation-menuimport { Bot, Clapperboard, ShoppingBag } from "lucide-react";import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger,} from "@/components/ui/navigation-menu";export function MainNav({ pathname }: { pathname: string }) { return ( <NavigationMenu aria-label="Main"> <NavigationMenuList> <NavigationMenuItem> <NavigationMenuTrigger>Products</NavigationMenuTrigger> <NavigationMenuContent className="w-80"> <NavigationMenuLink href="/bot" icon={<Bot />} title="Discord bot" description="Moderation, logs and slash commands" /> <NavigationMenuLink href="/clips" icon={<Clapperboard />} title="Clips" description="Record, trim and share highlights" /> <NavigationMenuLink href="/shop" icon={<ShoppingBag />} title="Shop" description="Merch and server boosts" /> </NavigationMenuContent> </NavigationMenuItem> <NavigationMenuItem> <NavigationMenuLink href="/blog" active={pathname.startsWith("/blog")} > Blog </NavigationMenuLink> </NavigationMenuItem> </NavigationMenuList> </NavigationMenu> );}<NavigationMenu>
Top navigation for a website: links and hover/click panels. Hover opens after a beat, then sliding along the bar switches panels at once; click and Enter toggle, Down opens into the panel, Left/Right walk the bar, Esc closes. Controlled with value/onValueChange ("" = closed).
defaultValuedefault""stringItem open at first render (uncontrolled).
onValueChange(value: string) => voidCalled with the opened item's value, or "".
valuestringControlled open item; "" = all closed.
| Prop | Type | Default |
|---|---|---|
defaultValueItem open at first render (uncontrolled). | string | "" |
onValueChangeCalled with the opened item's value, or "". | (value: string) => void | — |
valueControlled open item; "" = all closed. | string | — |
Also accepts every <nav> prop.
<NavigationMenuList>
The row of items. Its padding is the hover pill's inset.
Also accepts every <ul> prop (none of its own).
<NavigationMenuItem>
One top-level entry: a Trigger + Content pair, or a single Link.
valuestring | (readonly string[] & string)Stable id matched by
value; one is generated if omitted.
| Prop | Type | Default |
|---|---|---|
valueStable id matched by | string | (readonly string[] & string) | — |
Also accepts every <li> prop.
<NavigationMenuTrigger>
Opens its panel: hover (mouse), click, Enter or Space; Down opens and focuses the first link.
Also accepts every <button> prop (none of its own).
<NavigationMenuContent>
The panel. Size it with className (e.g. w-[32rem]); the viewport springs to it. While closed, its links stay in the HTML (hidden) so crawlers and no-JS readers still find them.
Also accepts every <div> prop (none of its own).
<NavigationMenuLink>
On the bar: a plain top-level link. In a panel: a row with optional icon, title and description (or your own children). Following it closes the menu.
activebooleanCurrent page:
aria-current="page"and the selected look.descriptionReactNodeMuted line under the title.
iconReactNodeIn a panel: rich row with a tile icon, title and one-line description.
titlestringBold first line of a panel link.
| Prop | Type | Default |
|---|---|---|
activeCurrent page: | boolean | — |
descriptionMuted line under the title. | ReactNode | — |
iconIn a panel: rich row with a tile icon, title and one-line description. | ReactNode | — |
titleBold first line of a panel link. | string | — |
Also accepts every <a> prop.
* required. Every component also takes className.