# Navigation menu

Site or app navigation with panels that open under their trigger, in Candor's theme (Radix). Arrow keys move between the triggers and links, Down opens a trigger's panel and moves into it, Escape closes it and returns focus to the trigger. The current page's link is marked `aria-current`. A link a person can't follow stays, marked and explained.

- Registry name: `navigation-menu` (`@candor/navigation-menu`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/navigation-menu

## Install

```bash
npx candor-ui add navigation-menu
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/navigation-menu`.
It brings in: locked, motion, render-prop, tokens, utils.
npm packages: class-variance-authority@^0.7.1, lucide-react@^1.67.0, radix-ui@^1.8.1.

## Example

```tsx
"use client"

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from "@/components/candor/ui/navigation-menu"

export default function Example() {
  return (
    <NavigationMenu aria-label="Workspace">
      <NavigationMenuList>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Reports</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul className="grid w-72 gap-1">
              <li>
                <NavigationMenuLink href="#weekly" active>
                  <span className="font-medium">Weekly summary</span>
                  <span className="text-xs text-muted-foreground">Runs, failures and spend for the past 7 days.</span>
                </NavigationMenuLink>
              </li>
              <li>
                <NavigationMenuLink href="#audit">
                  <span className="font-medium">Audit trail</span>
                  <span className="text-xs text-muted-foreground">Who changed what, and when.</span>
                </NavigationMenuLink>
              </li>
              <li>
                <NavigationMenuLink href="#export" locked="Exports are off for your workspace.">
                  <span className="font-medium">Scheduled exports</span>
                </NavigationMenuLink>
              </li>
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Team</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul className="grid w-60 gap-1">
              <li>
                <NavigationMenuLink href="#members">Members</NavigationMenuLink>
              </li>
              <li>
                <NavigationMenuLink href="#roles">Roles</NavigationMenuLink>
              </li>
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#docs" className={navigationMenuTriggerStyle()}>
            Docs
          </NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
    </NavigationMenu>
  )
}
```

## <NavigationMenu>

Props:

- `viewport?: boolean | undefined` (default `true`): Shows every panel in one shared box under the list that resizes between them. Defaults to true; false anchors each panel to its own trigger.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Root>`.

## <NavigationMenuContent>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Content>`.

## <NavigationMenuIndicator>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>`.

## <NavigationMenuItem>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Item>`.

## <NavigationMenuLink>

Props:

- `locked?: string | undefined`: Why the link can't be followed right now. When set, it stays focusable and marked `aria-disabled`, following it does nothing, and the reason is shown on hover and focus and read to screen readers (see `Locked`).

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Link>`, `RenderProp`.

## <NavigationMenuList>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.List>`.

## <NavigationMenuTrigger>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>`.

## <NavigationMenuViewport>

No props.

Also takes the props of `React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>`.

## Values

### navigationMenuTriggerStyle

```ts
const navigationMenuTriggerStyle: (props?: ClassProp | undefined) => string
```

## Motion

- **indicator** (overlay, fade): The small arrow under the open panel fades in when a panel opens and fades out when it closes.
- **open** (overlay, scale): With the shared viewport (the default), the box under the triggers fades in and scales up a little from the top when a panel opens, and fades out when it closes. With `viewport={false}` each panel is its own box and the first open is a plain fade. Under reduced motion both are a short fade.
- **slide** (overlay, slide): When the person moves from one open trigger to another, the new panel slides in from the side they moved towards and the old one slides out the same way; with no side to come from it fades. Under reduced motion it is a short fade.
- **turn** (reveal, size): The chevron beside a trigger turns when its panel opens and turns back when the panel closes. Under reduced motion it turns at once.

Set with `--cd-motion-navigation-menu-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
