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 marketing-site and public-page navigation. 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.