{"slug":"navigation-menu","title":"Navigation Menu","description":"Using the navigation menu machine in your project.","contentType":"component","framework":"svelte","content":"A navigation menu displays links with optional dropdown content.\n\n## Resources\n\n\n[Latest version: v1.39.1](https://www.npmjs.com/package/@zag-js/navigation-menu)\n[Logic Visualizer](https://zag-visualizer.vercel.app/navigation-menu)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/navigation-menu)\n\n\n\n**Features**\n\n- Supports basic (inline content) and viewport (shared viewport) patterns\n- Hover and click trigger support with configurable delays\n- Keyboard navigation with arrow keys, Tab, Home/End\n- Animated indicator that follows the active trigger\n- Smooth content animations with viewport positioning\n- Horizontal and vertical orientation\n- RTL (right-to-left) support\n- Dismissible with click outside or Escape key\n\n## Installation\n\nInstall the navigation menu package:\n\n```bash\nnpm install @zag-js/navigation-menu @zag-js/svelte\n# or\nyarn add @zag-js/navigation-menu @zag-js/svelte\n```\n\n## Anatomy\n\nCheck the navigation menu anatomy and part names.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nImport the navigation menu package:\n\n```tsx\nimport * as navigationMenu from \"@zag-js/navigation-menu\"\n```\n\nThe navigation menu package exports two key functions:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\n> Pass a unique `id` to `useMachine` so generated element ids stay predictable.\n\nThen use the framework integration helpers:\n\n```svelte\n<script lang=\"ts\">\n  import * as navigationMenu from \"@zag-js/navigation-menu\"\n  import { useMachine, normalizeProps } from \"@zag-js/svelte\"\n\n  const id = $props.id()\n  const service = useMachine(navigationMenu.machine, { id })\n  const api = $derived(navigationMenu.connect(service, normalizeProps))\n</script>\n\n<nav {...api.getRootProps()}>\n  <ul {...api.getListProps()}>\n    <!-- Item with dropdown content -->\n    <li {...api.getItemProps({ value: \"products\" })}>\n      <button {...api.getTriggerProps({ value: \"products\" })}>\n        Products\n      </button>\n      <div {...api.getContentProps({ value: \"products\" })}>\n        <a {...api.getLinkProps({ value: \"products\" })} href=\"/analytics\">\n          Analytics\n        </a>\n        <a {...api.getLinkProps({ value: \"products\" })} href=\"/marketing\">\n          Marketing\n        </a>\n      </div>\n    </li>\n\n    <!-- Simple link item -->\n    <li {...api.getItemProps({ value: \"pricing\" })}>\n      <a {...api.getLinkProps({ value: \"pricing\" })} href=\"/pricing\">\n        Pricing\n      </a>\n    </li>\n  </ul>\n</nav>\n```\n\nThe basic pattern places content directly within each item. This is suitable for\nsimple dropdown menus where each dropdown appears below its trigger.\n\n### Advanced pattern with viewport\n\nThe viewport pattern uses a shared viewport container for all content. This\nenables smooth transitions and better performance for complex navigation\nlayouts.\n\nIn this pattern:\n\n- Content is rendered inside a shared `viewport` element\n- The viewport automatically positions itself relative to the active trigger\n- You must include `triggerProxy` and `viewportProxy` for proper focus\n  management\n\n```svelte\n<script lang=\"ts\">\n  import * as navigationMenu from \"@zag-js/navigation-menu\"\n  import { useMachine, normalizeProps } from \"@zag-js/svelte\"\n\n  const id = $props.id()\n  const service = useMachine(navigationMenu.machine, { id })\n  const api = $derived(navigationMenu.connect(service, normalizeProps))\n</script>\n\n<nav {...api.getRootProps()}>\n  <div {...api.getIndicatorTrackProps()}>\n    <ul {...api.getListProps()}>\n      <!-- Item with trigger -->\n      <li {...api.getItemProps({ value: \"products\" })}>\n        <button {...api.getTriggerProps({ value: \"products\" })}>\n          Products\n        </button>\n        <!-- Focus management proxies -->\n        <span {...api.getTriggerProxyProps({ value: \"products\" })} />\n        <span {...api.getViewportProxyProps({ value: \"products\" })} />\n      </li>\n\n      <!-- Simple link -->\n      <li {...api.getItemProps({ value: \"pricing\" })}>\n        <a {...api.getLinkProps({ value: \"pricing\" })} href=\"/pricing\">\n          Pricing\n        </a>\n      </li>\n\n      <!-- Indicator -->\n      <div {...api.getIndicatorProps()}>\n        <div {...api.getArrowProps()} />\n      </div>\n    </ul>\n  </div>\n\n  <!-- Shared viewport for all content -->\n  <div {...api.getViewportPositionerProps()}>\n    <div {...api.getViewportProps()}>\n      <!-- Content for products -->\n      <div {...api.getContentProps({ value: \"products\" })}>\n        <a {...api.getLinkProps({ value: \"products\" })} href=\"/analytics\">\n          Analytics\n        </a>\n        <a {...api.getLinkProps({ value: \"products\" })} href=\"/marketing\">\n          Marketing\n        </a>\n      </div>\n    </div>\n  </div>\n</nav>\n```\n\n**When to use viewport pattern:**\n\n- Complex navigation with varying content sizes\n- Smooth animated transitions between different content\n- Header navigation bars (like on e-commerce sites)\n- When you want a single shared container for all dropdowns\n\n### Controlling the navigation menu\n\nTo control which item is currently open, pass the `value` and `onValueChange`\nproperties to the machine.\n\n```svelte {3,5-10}\n<script lang=\"ts\">\n  let value = $state(\"\")\n\n  const service = useMachine(navigationMenu.machine, {\n    id: \"nav\",\n    get value() {\n      return value\n    },\n    onValueChange(details) {\n      value = details.value\n    },\n  })\n\n  const api = $derived(navigationMenu.connect(service, normalizeProps))\n</script>\n\n<div>\n  <button onclick={() => value = \"products\"}>Open Products</button>\n  <button onclick={() => value = \"\"}>Close All</button>\n\n  <nav {...api.getRootProps()}>\n    <!-- ... navigation items ... -->\n  </nav>\n</div>\n```\n\n### Setting initial open item\n\nUse `defaultValue` to start with a specific item open.\n\n```tsx\nconst service = useMachine(navigationMenu.machine, {\n  defaultValue: \"products\",\n})\n```\n\n### Listening for value changes\n\nWhen the open item changes, the `onValueChange` callback is invoked with the new\nvalue.\n\n```tsx {3-6}\nconst service = useMachine(navigationMenu.machine, {\n  id: \"nav\",\n  onValueChange(details) {\n    // details => { value: string }\n    console.log(\"Current open item:\", details.value)\n  },\n})\n```\n\n### Adding an animated indicator\n\nTo show a visual indicator that animates to the active trigger, render the\nindicator within the list container:\n\n```svelte {3-5}\n<nav {...api.getRootProps()}>\n  <div {...api.getListProps()}>\n    <!-- ... items ... -->\n\n    <div {...api.getIndicatorProps()}>\n      <div {...api.getArrowProps()} />\n    </div>\n  </div>\n</nav>\n```\n\nThe indicator automatically transitions to match the active trigger's position\nand size using CSS variables.\n\n### Configuring hover delays\n\nYou can customize the delay before opening and closing on hover:\n\n```tsx {2-3}\nconst service = useMachine(navigationMenu.machine, {\n  openDelay: 300, // Delay before opening on hover (default: 200ms)\n  closeDelay: 400, // Delay before closing on pointer leave (default: 300ms)\n})\n```\n\n**Tip**: Longer delays provide a more forgiving user experience but can feel\nless responsive.\n\n### Disabling hover or click triggers\n\nYou can disable hover or click triggers independently:\n\n```tsx {2-6}\nconst service = useMachine(navigationMenu.machine, {\n  disableHoverTrigger: true, // Only open on click\n  // OR\n  disableClickTrigger: true, // Only open on hover\n  // OR\n  disablePointerLeaveClose: true, // Prevents closing when pointer leaves\n})\n```\n\n- `disableHoverTrigger` — Prevents opening on hover (click only)\n- `disableClickTrigger` — Prevents opening on click (hover only)\n- `disablePointerLeaveClose` — Prevents closing when pointer leaves\n\n### Changing orientation\n\nThe default orientation is horizontal. To create a vertical navigation menu:\n\n```tsx {2}\nconst service = useMachine(navigationMenu.machine, {\n  orientation: \"vertical\",\n})\n```\n\nThis affects keyboard navigation (arrow keys) and indicator positioning.\n\n### Disabling items\n\nTo disable a navigation item, pass `disabled: true` to the item props:\n\n```tsx\n<div {...api.getItemProps({ value: \"products\", disabled: true })}>\n  <button {...api.getTriggerProps({ value: \"products\", disabled: true })}>\n    Products\n  </button>\n</div>\n```\n\nDisabled items cannot be opened and are skipped during keyboard navigation.\n\n### Indicating current page\n\nTo highlight the current page link, use the `current` prop:\n\n```tsx\n<a {...api.getLinkProps({ value: \"products\", current: true })}>Products</a>\n```\n\nThis adds `data-current` attribute and `aria-current=\"page\"` for accessibility.\n\n### Controlling link close behavior\n\nUse `closeOnClick` in `getLinkProps` to keep content open after link click.\n\n```tsx\n<a\n  {...api.getLinkProps({\n    value: \"products\",\n    closeOnClick: false,\n  })}\n>\n  Products\n</a>\n```\n\n### Aligning the shared viewport\n\nWhen using the viewport pattern, set `align` on `getViewportProps` or\n`getViewportPositionerProps`.\n\n```tsx\n<div\n  {...api.getViewportProps({\n    align: \"start\",\n  })}\n/>\n```\n\n### RTL support\n\nThe navigation menu supports right-to-left languages. Set the `dir` property to\n`rtl`:\n\n```tsx {2}\nconst service = useMachine(navigationMenu.machine, {\n  dir: \"rtl\",\n})\n```\n\n## Styling guide\n\nEach part includes a `data-part` attribute you can target in CSS.\n\n### Open and closed states\n\nWhen content is open or closed, it receives a `data-state` attribute:\n\n```css\n[data-part=\"content\"][data-state=\"open|closed\"] {\n  /* Styles for open or closed content */\n}\n\n[data-part=\"trigger\"][data-state=\"open|closed\"] {\n  /* Styles for open or closed trigger */\n}\n\n[data-part=\"viewport\"][data-state=\"open|closed\"] {\n  /* Styles for viewport open/closed state */\n}\n```\n\n### Selected item state\n\nWhen an item is selected (open), it receives `data-state=\"open\"`:\n\n```css\n[data-part=\"item\"][data-state=\"open\"] {\n  /* Styles for open item */\n}\n```\n\n### Disabled state\n\nDisabled items have a `data-disabled` attribute:\n\n```css\n[data-part=\"item\"][data-disabled] {\n  /* Styles for disabled items */\n}\n\n[data-part=\"trigger\"][data-disabled] {\n  /* Styles for disabled triggers */\n}\n```\n\n### Orientation styles\n\nAll parts have a `data-orientation` attribute:\n\n```css\n[data-part=\"root\"][data-orientation=\"horizontal|vertical\"] {\n  /* Orientation-specific styles */\n}\n\n[data-part=\"list\"][data-orientation=\"horizontal\"] {\n  display: flex;\n  flex-direction: row;\n}\n\n[data-part=\"list\"][data-orientation=\"vertical\"] {\n  display: flex;\n  flex-direction: column;\n}\n```\n\n### Current link state\n\nLinks marked as current have a `data-current` attribute:\n\n```css\n[data-part=\"link\"][data-current] {\n  /* Styles for current page link */\n}\n```\n\n### Styling the indicator\n\nThe indicator uses CSS variables for positioning and sizing:\n\n```css\n[data-part=\"indicator\"] {\n  position: absolute;\n  transition:\n    translate 250ms ease,\n    width 250ms ease,\n    height 250ms ease;\n}\n\n[data-part=\"indicator\"][data-orientation=\"horizontal\"] {\n  left: 0;\n  translate: var(--trigger-x) 0;\n  width: var(--trigger-width);\n}\n\n[data-part=\"indicator\"][data-orientation=\"vertical\"] {\n  top: 0;\n  translate: 0 var(--trigger-y);\n  height: var(--trigger-height);\n}\n```\n\n### Styling the viewport\n\nThe viewport uses CSS variables for positioning and sizing:\n\n```css\n[data-part=\"viewport\"] {\n  position: absolute;\n  width: var(--viewport-width);\n  height: var(--viewport-height);\n  transition:\n    width 300ms ease,\n    height 300ms ease;\n}\n```\n\n### Arrow styling\n\nThe arrow can be styled using CSS variables:\n\n```css\n[data-part=\"root\"] {\n  --arrow-size: 20px;\n}\n\n[data-part=\"arrow\"] {\n  width: var(--arrow-size);\n  height: var(--arrow-size);\n  background: white;\n  rotate: 45deg;\n}\n```\n\n### Motion attributes\n\nWhen using the viewport pattern, content elements receive `data-motion`\nattributes for directional animations:\n\n```css\n[data-part=\"content\"][data-motion=\"from-start\"] {\n  animation: slideFromStart 250ms ease;\n}\n\n[data-part=\"content\"][data-motion=\"from-end\"] {\n  animation: slideFromEnd 250ms ease;\n}\n\n[data-part=\"content\"][data-motion=\"to-start\"] {\n  animation: slideToStart 250ms ease;\n}\n\n[data-part=\"content\"][data-motion=\"to-end\"] {\n  animation: slideToEnd 250ms ease;\n}\n```\n\n**Tip**: The motion direction indicates where the content is coming from\n(from-start/from-end) or going to (to-start/to-end), enabling context-aware\nanimations when switching between items.\n\n## Methods and Properties\n\n### Machine Context\n\nThe navigation menu machine exposes the following context properties:\n\n**`translations`**\nType: `IntlTranslations`\nDescription: Specifies the localized strings that identifies the accessibility elements and their states\n\n**`ids`**\nType: `Partial<{ root: string; list: string; item: string; trigger: (value: string) => string; content: (value: string) => string; viewport: string; }>`\nDescription: The ids of the elements in the machine.\n\n**`value`**\nType: `string`\nDescription: The controlled value of the navigation menu\n\n**`defaultValue`**\nType: `string`\nDescription: The default value of the navigation menu.\nUse when you don't want to control the value of the menu.\n\n**`onValueChange`**\nType: `(details: ValueChangeDetails) => void`\nDescription: Function called when the value of the menu changes\n\n**`openDelay`**\nType: `number`\nDescription: The delay before the menu opens\n\n**`closeDelay`**\nType: `number`\nDescription: The delay before the menu closes\n\n**`disableClickTrigger`**\nType: `boolean`\nDescription: Whether to disable the click trigger\n\n**`disableHoverTrigger`**\nType: `boolean`\nDescription: Whether to disable the hover trigger\n\n**`disablePointerLeaveClose`**\nType: `boolean`\nDescription: Whether to disable the pointer leave close\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n**`orientation`**\nType: `\"horizontal\" | \"vertical\"`\nDescription: The orientation of the element.\n\n### Machine API\n\nThe navigation menu `api` exposes the following methods:\n\n**`value`**\nType: `string`\nDescription: The current value of the menu\n\n**`setValue`**\nType: `(value: string) => void`\nDescription: Sets the value of the menu\n\n**`open`**\nType: `boolean`\nDescription: Whether the menu is open\n\n**`isViewportRendered`**\nType: `boolean`\nDescription: Whether the viewport is rendered\n\n**`getViewportNode`**\nType: `() => HTMLElement`\nDescription: Gets the viewport node element\n\n**`orientation`**\nType: `Orientation`\nDescription: The orientation of the menu\n\n**`reposition`**\nType: `VoidFunction`\nDescription: Function to reposition the viewport\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: root\n**`data-orientation`**: The orientation of the navigation-menu\n\n**`List`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: list\n**`data-orientation`**: The orientation of the list\n\n**`Item`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: item\n**`data-value`**: The value of the item\n**`data-state`**: \"open\" | \"closed\"\n**`data-orientation`**: The orientation of the item\n**`data-disabled`**: Present when disabled\n\n**`Indicator`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: indicator\n**`data-state`**: \"open\" | \"closed\"\n**`data-orientation`**: The orientation of the indicator\n\n**`Arrow`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: arrow\n**`data-orientation`**: The orientation of the arrow\n\n**`Trigger`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: trigger\n**`data-trigger-proxy-id`**: \n**`data-value`**: The value of the item\n**`data-state`**: \"open\" | \"closed\"\n**`data-disabled`**: Present when disabled\n\n**`TriggerProxy`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: trigger-proxy\n**`data-trigger-proxy`**: \n**`data-trigger-id`**: \n\n**`Link`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: link\n**`data-value`**: The value of the item\n**`data-current`**: Present when current\n\n**`Content`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: content\n**`data-state`**: \"open\" | \"closed\"\n**`data-orientation`**: The orientation of the content\n**`data-value`**: The value of the item\n\n**`ViewportPositioner`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: viewport-positioner\n**`data-orientation`**: The orientation of the viewportpositioner\n**`data-align`**: \n\n**`Viewport`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: viewport\n**`data-state`**: \"open\" | \"closed\"\n**`data-orientation`**: The orientation of the viewport\n**`data-align`**: \n\n**`ItemIndicator`**\n\n**`data-scope`**: navigation-menu\n**`data-part`**: item-indicator\n**`data-state`**: \"open\" | \"closed\"\n**`data-orientation`**: The orientation of the item\n**`data-value`**: The value of the item\n\n### CSS Variables\n\n<CssVarTable name=\"navigation-menu\" />\n\n## Accessibility\n\n### Keyboard Interactions\n\n**`ArrowDown`**\nDescription: When focus is on trigger (vertical orientation), moves focus to the next trigger.\n\n**`ArrowUp`**\nDescription: When focus is on trigger (vertical orientation), moves focus to the previous trigger.\n\n**`ArrowRight`**\nDescription: <span>When focus is on trigger (horizontal orientation), moves focus to the next trigger.<br />When focus is on content, moves focus to the next link.</span>\n\n**`ArrowLeft`**\nDescription: <span>When focus is on trigger (horizontal orientation), moves focus to the previous trigger.<br />When focus is on content, moves focus to the previous link.</span>\n\n**`Home`**\nDescription: <span>When focus is on trigger, moves focus to the first trigger.<br />When focus is on content, moves focus to the first link.</span>\n\n**`End`**\nDescription: <span>When focus is on trigger, moves focus to the last trigger.<br />When focus is on content, moves focus to the last link.</span>","package":"@zag-js/navigation-menu","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/navigation-menu.mdx"}