Navigation Menu
Primary site navigation with optional dropdown panels.
@garn/ui/navigation-menuOn this page
Default
A trigger with a link panel plus a plain top-level link.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add navigation-menu- Registry
navigation-menu- Deps
@radix-ui/react-navigation-menuclass-variance-authoritylucide-react- Registry deps
utils
Import
import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, navigationMenuTriggerStyle } from "@garn/ui/navigation-menu";Anatomy
The parts this component is built from — compose them to assemble it.
NavigationMenuRequired[data-slot="navigation-menu"]Root — owns which item is open and renders the shared Viewport.
NavigationMenuListRequired[data-slot="navigation-menu-list"]The horizontal list of top-level items.
NavigationMenuItemRequired[data-slot="navigation-menu-item"]One top-level entry — a link, or a trigger + content panel.
NavigationMenuTrigger[data-slot="navigation-menu-trigger"]Opens an item's dropdown panel (with a rotating chevron).
NavigationMenuContent[data-slot="navigation-menu-content"]The dropdown panel; rendered into the shared Viewport.
NavigationMenuLink[data-slot="navigation-menu-link"]A navigation link (asChild to wrap your router's Link). Apply navigationMenuTriggerStyle() to match trigger styling.
NavigationMenuIndicator[data-slot="navigation-menu-indicator"]The little arrow pointing at the active trigger.
NavigationMenuViewport[data-slot="navigation-menu-viewport"]The shared, animated container all panels render into (rendered by the Root).
navigationMenuTriggerStyleA cva() class helper (no variants) so plain links can match the trigger's look.
Props
The component's public props and their types.
No component-specific props — this is a thin wrapper over its native element.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="navigation-menu"]States
| Selector | State | Description |
|---|---|---|
[data-state=open] | open | An open trigger/panel; the trigger's chevron rotates. |
[data-state=closed] | closed | |
[data-active] | active-link | The link for the current page (set `active` on NavigationMenuLink). |
| Property | Token | Tier |
|---|---|---|
triggerHeight | --garn-control-h-md | semantic · air |
listGap | --garn-gap-inline | semantic · air |
motion | --garn-motion-base | semantic |
When to use
Primary site navigation with optional rich dropdown panels — a top header nav or mega-menu.
Reach for something else when
- Application command menus (use menubar)
- a single button-triggered menu (use dropdown-menu)
- in-page view switching (use tabs)
- a plain list of links (just use a <nav> + links).
Overview
NavigationMenu is primary site navigation with optional rich dropdown panels — a top header nav or mega-menu. NavigationMenu owns which item is open and hosts a shared Viewport; each NavigationMenuItem is either a direct link or a NavigationMenuTrigger + NavigationMenuContent panel for grouped destinations. It's for navigating to places; for menus of actions use DropdownMenu/Menubar, and for in-page section switching use Tabs.
Guidelines
Use it to navigate, not to run actions. Its items lead to pages/sections; commands belong in a DropdownMenu or Menubar. Keep the top level short and label items by destination, not feature jargon.
Keep dropdown/mega panels scannable. When an item expands a panel, group links under clear headings and keep the panel shallow — a wall of links is as hard to use as no menu. Mark the current section so users know where they are.
Make every destination reachable by keyboard and on mobile. Triggers open on focus/Enter and the panel is navigable; don't gate links behind hover-only reveals. Provide a responsive collapse (a Sheet/drawer) so the nav still works on small screens.
Best practices
- Use NavigationMenuLink (asChild) for real navigation, and navigationMenuTriggerStyle() so links match triggers.
- Reserve NavigationMenuTrigger/Content for items that actually reveal a panel.
- Mark the current destination with
active; label the nav if the page has more than one.
- Don't use it for in-page tabs or app command bars.
- Don't put a trigger on items that are just links.
- Don't bury primary destinations behind triggers unnecessarily.
Content guidelines
- Use short, destination-named links and keep top-level labels broad and scannable.
- In mega-menu panels, group links under clear category headings.
Troubleshooting
A simple destination opens an empty/needless dropdown.
Cause. It was built with a Trigger + Content instead of a plain link.
Fix. Use NavigationMenuLink (with navigationMenuTriggerStyle()) for plain destinations; reserve triggers for panels.
Client-side routing doesn't fire from a nav link.
Cause. A raw <a> was used instead of the router's Link.
Fix. Wrap your router's Link with NavigationMenuLink asChild.
Accessibility
- Role
navigation- Focus
- Triggers and links show a focus ring; panels animate in the shared Viewport.
Keyboard
| Tab | Move between links and triggers. |
| EnterSpaceArrowDown | Open the focused item's panel. |
| Escape | Close the open panel. |
- The Root is a <nav> landmark; items are links or disclosure triggers that reveal aria-controlled panels.
- Use NavigationMenuLink for real navigation (asChild to wrap a router Link); mark the current page with `active`.
- Only use a trigger for items that reveal a panel — plain destinations are just links.