Components
Navigation Menu
A horizontal list of links and dropdowns for primary site navigation, built on Radix with full keyboard and pointer support.
When to use it
This is the navigation of landing pages and other public pages. Inside a signed-in Comitor app the equivalent job belongs to Sidebar and AppLauncher from @comitor/ui/shell, which know about entitlements and the current workspace. Unlike most overlay components, this module ships without a 'use client' directive — it only declares layout, and the state lives inside the Radix package — so the parts and the navigationMenuTriggerStyle() helper can both be used directly from a Server Component.
Basic usage
Pair a NavigationMenuTrigger with NavigationMenuContent to reveal a dropdown panel of links. Hover or focus the trigger to open it.
Simple links
For top-level links without a dropdown, render a NavigationMenuLink styled with navigationMenuTriggerStyle() so it matches the trigger height and padding.
Inline content
Pass viewport={false} on the root to drop the shared floating viewport and render each panel inline, directly beneath its own item.
Indicator
Drop a NavigationMenuIndicator as the last child of the list and a small arrow slides along the bar to whichever trigger is open. It is state information, not decoration, so the package draws it with the control-edge token instead of the decorative border grey: the arrow then follows the reader's palette and darkens under data-contrast="high", where a hard-coded scale class would stay pinned to the default one.
Props
The parts exported from @comitor/ui. Every part forwards the props of the Radix primitive it wraps, plus the native props of the element it renders.
| Prop | Type | Default | Description |
|---|---|---|---|
NavigationMenu | NavigationMenuPrimitive.Root | — | The root bar. Renders NavigationMenuViewport for you unless viewport is false. |
NavigationMenuList | NavigationMenuPrimitive.List | — | The <ul> holding the items — and the indicator, when you use one. |
NavigationMenuItem | NavigationMenuPrimitive.Item | — | One <li>: either a trigger plus content, or a bare link. |
NavigationMenuTrigger | NavigationMenuPrimitive.Trigger | — | Opens its content on hover or keyboard. Appends a chevron that rotates while open. |
NavigationMenuContent | NavigationMenuPrimitive.Content | — | The panel of links. Styles itself as a popover only when viewport is false. |
NavigationMenuLink | NavigationMenuPrimitive.Link | — | A link inside a panel, or a top-level link when styled with navigationMenuTriggerStyle(). |
NavigationMenuIndicator | NavigationMenuPrimitive.Indicator | — | The small arrow that points at the open trigger. Place it last inside the list. |
NavigationMenuViewport | NavigationMenuPrimitive.Viewport | — | The shared panel the content animates into. Rendered automatically by the root; only mount it yourself if you need it somewhere else in the tree. |
navigationMenuTriggerStyle | () => string | — | The cva helper behind the trigger. Callable from a Server Component — the module carries no "use client". |
NavigationMenu (root)
| Prop | Type | Default | Description |
|---|---|---|---|
viewport | boolean | true | Package addition. When true, panels render into one shared viewport below the bar that resizes between items. Set it to false to let each item pop its own panel — the matching styles hang off group-data-[viewport=false]. |
value | string | — | Controlled value of the currently open item. |
defaultValue | string | — | Value of the item open on first render, for uncontrolled usage. |
onValueChange | (value: string) => void | — | Called when the open item changes. |
delayDuration | number | 200 | Milliseconds a pointer must rest on a trigger before its content opens. |
skipDelayDuration | number | 300 | Window after closing during which moving to another trigger opens it instantly. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout axis, which also decides which arrow keys move between triggers. |
dir | 'ltr' | 'rtl' | 'ltr' | Reading direction, affecting the arrow keys and the open animation. |
NavigationMenuLink
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Marks the link as the current page: Radix stamps aria-current="page" and an empty data-active attribute on the anchor. Style the current link yourself — data-active carries no value, so the packaged data-[active=true] rules do not match it. |
href | string | — | Destination URL, forwarded to the underlying <a>. |
onSelect | (event: Event) => void | — | Called when the link is selected; the menu closes afterwards. |
asChild | boolean | false | Merges the link props onto its single child — the way to wrap a framework link such as next/link. |
className | string | — | Additional Tailwind classes. Pass navigationMenuTriggerStyle() here for a top-level link. |
Accessibility
- Built on Radix Navigation Menu, which exposes the menu with the correct ARIA roles and manages
aria-expandedon each trigger automatically. - Full keyboard support: Tab moves focus into and out of the bar, ← / → move between triggers, ↓ opens a panel and moves into it, and Esc closes the active dropdown.
- Triggers and links render a
focus-visible:ring-2 ring-ring/50outline with a background-coloured offset — the ring token is the brand gold, so the focus outline stays recognisable in both themes and both contrast palettes. - The indicator arrow is drawn with a role token rather than a scale class, so it tracks the reader's chosen palette; it is redundant with the trigger's own open state, which assistive tech reads from
aria-expanded. - Set
activeon theNavigationMenuLinkfor the current page: Radix rendersaria-current="page", which is what a screen reader announces. Add your own visible treatment alongside it — location must not be signalled to sighted users by nothing at all.