{"slug":"nested-menu","title":"Nested Menu","description":"Using the menu machine for nested menus in your project.","contentType":"component","framework":"svelte","content":"An accessible dropdown and context menu that is used to display a list of\nactions or options that a user can choose.\n\n## Resources\n\n\n[Latest version: v1.39.1](https://www.npmjs.com/package/@zag-js/menu)\n[Logic Visualizer](https://zag-visualizer.vercel.app/menu)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/menu)\n\n\n\n**Features**\n\n- Supports items, labels, groups of items\n- Focus is fully managed using `aria-activedescendant` pattern\n- Typeahead to allow focusing items by typing text\n- Keyboard navigation support including arrow keys, home/end, page up/down\n\n## Installation\n\nInstall the menu package:\n\n```bash\nnpm install @zag-js/menu @zag-js/svelte\n# or\nyarn add @zag-js/menu @zag-js/svelte\n```\n\n## Anatomy\n\nCheck the 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 menu package:\n\n```tsx\nimport * as menu from \"@zag-js/menu\"\n```\n\nThe menu package exports two key functions:\n\n- `machine` - Behavior logic for the menu.\n- `connect` - Maps behavior 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- Destructure the service returned from `useMachine`.\n- Use the exposed `setParent` and `setChild` functions provided by the menu's\n  connect function to assign the parent and child menus respectively.\n- Create trigger items using `api.getTriggerItemProps(...)`.\n\nWhen building nested menus, you'll need to use:\n\n- `setParent(...)` - Registers the parent menu on the child menu.\n- `setChild(...)` - Registers the child menu on the parent menu.\n\n```svelte\n<script lang=\"ts\">\n  import * as menu from \"@zag-js/menu\"\n  import { portal, normalizeProps, useMachine } from \"@zag-js/svelte\"\n  import { onMount } from \"svelte\"\n\n  const id = $props.id()\n\n  // Level 1 - File Menu\n  const fileService = useMachine(menu.machine, ({ id: `${id}-1`, \"aria-label\": \"File\" }))\n\n  const fileMenu = $derived(menu.connect(fileService, normalizeProps))\n\n  // Level 2 - Share Menu\n  const shareService = useMachine(menu.machine, ({ id: `${id}-2`, \"aria-label\": \"Share\" }))\n\n  const shareMenu = $derived(menu.connect(shareService, normalizeProps))\n\n  onMount(() => {\n    fileMenu.setChild(shareService)\n    shareMenu.setParent(fileService)\n  })\n\n  // Share menu trigger\n  const shareMenuTriggerProps = $derived(fileMenu.getTriggerItemProps(shareMenu))\n</script>\n\n<button {...fileMenu.getTriggerProps()}>Click me</button>\n\n<div use:portal {...fileMenu.getPositionerProps()}>\n  <ul {...fileMenu.getContentProps()}>\n    <li {...fileMenu.getItemProps({ value: \"new-tab\" })}>New tab</li>\n    <li {...fileMenu.getItemProps({ value: \"new-win\" })}>New window</li>\n    <li {...shareMenuTriggerProps}>Share</li>\n    <li {...fileMenu.getItemProps({ value: \"print\" })}>Print...</li>\n    <li {...fileMenu.getItemProps({ value: \"help\" })}>Help</li>\n  </ul>\n</div>\n\n<div use:portal {...shareMenu.getPositionerProps()}>\n  <ul {...shareMenu.getContentProps()}>\n    <li {...shareMenu.getItemProps({ value: \"messages\" })}>Messages</li>\n    <li {...shareMenu.getItemProps({ value: \"airdrop\" })}>Airdrop</li>\n    <li {...shareMenu.getItemProps({ value: \"whatsapp\" })}>WhatsApp</li>\n  </ul>\n</div>\n```\n\n### Controlling open state\n\nUse `open` and `onOpenChange` to control the open state.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  open,\n  onOpenChange(details) {\n    // details => { open: boolean }\n    setOpen(details.open)\n  },\n})\n```\n\n### Default open state\n\nUse `defaultOpen` for an uncontrolled initial state.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  defaultOpen: true,\n})\n```\n\n### Listening for highlighted changes\n\nUse `onHighlightChange` to react when keyboard or pointer highlight changes.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  onHighlightChange(details) {\n    // details => { highlightedValue: string | null }\n    console.log(details.highlightedValue)\n  },\n})\n```\n\n### Setting the initial highlighted item\n\nUse `defaultHighlightedValue` to set the initially highlighted menu item.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  defaultHighlightedValue: \"copy\",\n})\n```\n\n### Listening for item selection\n\nUse `onSelect` to react when an item is selected.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  onSelect(details) {\n    // details => { value: string }\n    console.log(details.value)\n  },\n})\n```\n\n### Positioning submenus\n\nUse `positioning` to configure submenu placement.\n\n```tsx\nconst service = useMachine(menu.machine, {\n  positioning: { placement: \"right-start\" },\n})\n```\n\n## Styling guide\n\nEach menu part includes a `data-part` attribute you can target in CSS.\n\n### Highlighted item state\n\nWhen an item is highlighted, via keyboard navigation or pointer, it is given a\n`data-highlighted` attribute.\n\n```css\n[data-part=\"item\"][data-highlighted] {\n  /* styles for highlighted state */\n}\n\n[data-part=\"item\"][data-type=\"radio|checkbox\"][data-highlighted] {\n  /* styles for highlighted state */\n}\n```\n\n### Disabled item state\n\nWhen an item or an option item is disabled, it is given a `data-disabled`\nattribute.\n\n```css\n[data-part=\"item\"][data-disabled] {\n  /* styles for disabled state */\n}\n\n[data-part=\"item\"][data-type=\"radio|checkbox\"][data-disabled] {\n  /* styles for disabled state */\n}\n```\n\n### Using arrows\n\nWhen using arrows within the menu, you can style it using CSS variables.\n\n```css\n[data-part=\"arrow\"] {\n  --arrow-size: 20px;\n  --arrow-background: red;\n}\n```\n\n### Checked option item state\n\nWhen an option item is checked, it is given a `data-state` attribute.\n\n```css\n[data-part=\"item\"][data-type=\"radio|checkbox\"][data-state=\"checked\"] {\n  /* styles for checked state */\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe menu machine exposes the following context properties:\n\n<ContextTable name=\"menu\" />\n\n### Machine API\n\nThe menu `api` exposes the following methods:\n\n<ApiTable name=\"menu\" />\n\n### Data Attributes\n\n<DataAttrTable name=\"menu\" />\n\n### CSS Variables\n\n<CssVarTable name=\"menu\" />\n\n## Accessibility\n\nUses\n[aria-activedescendant](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant/)\npattern to manage focus movement among menu items.\n\n### Keyboard Interactions\n\n**`Space`**\nDescription: Opens/closes the nested menu.\n\n**`Enter`**\nDescription: Opens/closes the nested menu.\n\n**`ArrowDown`**\nDescription: Moves focus to the next item.\n\n**`ArrowUp`**\nDescription: Moves focus to the previous item.\n\n**`ArrowRight`**\nDescription: Opens the nested menu.\n\n**`ArrowLeft`**\nDescription: Closes the nested menu.\n\n**`Esc`**\nDescription: Closes the nested menu and moves focus to the parent menu item.","package":"@zag-js/menu","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/nested-menu.mdx"}