Menu
A strict action menu with managed focus, keyboard navigation, and menu button semantics.
Usage
Menu is a strict action menu primitive. Use Menu, MenuTrigger, MenuContent, and MenuItem together.
<div class="zui-menu" data-state="open">
<button class="zui-button zui-button-variant-outline zui-menu-trigger" aria-haspopup="menu" aria-expanded="true">
Open menu <i class="ph ph-caret-down"></i>
</button>
<div class="zui-menu-content" role="menu" data-state="open">
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem">Profile</button>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem">Settings</button>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem">Logout</button>
</div>
</div> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/react'
<Menu>
<MenuTrigger>Open menu <i className="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuContent>
</Menu> ---
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/astro'
---
<Menu>
<MenuTrigger>Open menu <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuContent>
</Menu> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/solid'
<Menu>
<MenuTrigger>Open menu <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuContent>
</Menu> <script lang="ts">
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/svelte'
</script>
<Menu>
<MenuTrigger>Open menu <i class="ph ph-caret-down"></i></MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuContent>
</Menu> <template>
<Menu>
<MenuTrigger>Open menu <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuContent>
</Menu>
</template>
<script setup>
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/vue'
</script> With icons and links
<div class="zui-menu" data-state="open">
<button class="zui-button zui-menu-trigger" aria-haspopup="menu" aria-expanded="true">Account <i class="ph ph-caret-down"></i></button>
<div class="zui-menu-content" role="menu" data-state="open">
<a href="#" class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem"><i class="ph ph-user"></i> Profile</a>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem"><i class="ph ph-gear"></i> Settings</button>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem"><i class="ph ph-sign-out"></i> Logout</button>
</div>
</div> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/react'
<Menu>
<MenuTrigger>Account <i className="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem href="#"><i className="ph ph-user" /> Profile</MenuItem>
<MenuItem><i className="ph ph-gear" /> Settings</MenuItem>
<MenuItem><i className="ph ph-sign-out" /> Logout</MenuItem>
</MenuContent>
</Menu> ---
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/astro'
---
<Menu>
<MenuTrigger>Account <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem href="#"><i class="ph ph-user" /> Profile</MenuItem>
<MenuItem><i class="ph ph-gear" /> Settings</MenuItem>
<MenuItem><i class="ph ph-sign-out" /> Logout</MenuItem>
</MenuContent>
</Menu> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/solid'
<Menu>
<MenuTrigger>Account <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem href="#"><i class="ph ph-user" /> Profile</MenuItem>
<MenuItem><i class="ph ph-gear" /> Settings</MenuItem>
<MenuItem><i class="ph ph-sign-out" /> Logout</MenuItem>
</MenuContent>
</Menu> <script lang="ts">
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/svelte'
</script>
<Menu>
<MenuTrigger>Account <i class="ph ph-caret-down"></i></MenuTrigger>
<MenuContent>
<MenuItem href="#"><i class="ph ph-user"></i> Profile</MenuItem>
<MenuItem><i class="ph ph-gear"></i> Settings</MenuItem>
<MenuItem><i class="ph ph-sign-out"></i> Logout</MenuItem>
</MenuContent>
</Menu> <template>
<Menu>
<MenuTrigger>Account <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem href="#"><i class="ph ph-user" /> Profile</MenuItem>
<MenuItem><i class="ph ph-gear" /> Settings</MenuItem>
<MenuItem><i class="ph ph-sign-out" /> Logout</MenuItem>
</MenuContent>
</Menu>
</template>
<script setup>
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/vue'
</script> Anatomy
Menu— root state containerMenuTrigger— button that opens and closes the menuMenuContent— popup surface withrole="menu"MenuItem— actionable row rendered as abuttonora
Common props
Menu
defaultOpen— uncontrolled initial open stateopen— controlled open statedisabled— disables the whole menuside—top | right | bottom | leftalign—start | center | enddir—ltr | rtl
MenuTrigger
MenuTrigger accepts the same visual props as a ZUI button:
variantcolorsizeshapeicon
MenuItem
href— render as a link itemdisabled— prevent focus/activationtextValue— explicit label used for typeahead matching
Positioning
Use side and align on Menu to change where the menu surface appears.
---
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/astro'
---
<Menu side="top" align="end">
<MenuTrigger variant="outline">More <i class="ph ph-dots-three" /></MenuTrigger>
<MenuContent>
<MenuItem>Rename</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuItem>Archive</MenuItem>
</MenuContent>
</Menu> Typeahead
When the menu is open, typing letters moves focus to the next matching enabled item.
By default, matching uses each item's text content. Pass textValue when the visible label includes icons, extra formatting, or text that should not drive matching.
---
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/astro'
---
<Menu>
<MenuTrigger variant="outline">Jump to item</MenuTrigger>
<MenuContent>
<MenuItem textValue="profile"><i class="ph ph-user" /> Profile</MenuItem>
<MenuItem textValue="settings"><i class="ph ph-gear" /> Settings</MenuItem>
<MenuItem textValue="sign out"><i class="ph ph-sign-out" /> Sign out</MenuItem>
</MenuContent>
</Menu> Keyboard behavior
Enter,Space,ArrowDownon the trigger open the menu and focus the first enabled item.ArrowUpon the trigger opens the menu and focuses the last enabled item.- Inside the menu, use
ArrowDown,ArrowUp,Home, andEndto move focus. EnterandSpaceactivate the focused item.Escapecloses the menu and returns focus to the trigger.Tabcloses the menu and continues normal tab order.- Typeahead is supported for enabled items.
Dismissal behavior
The menu closes on outside click, trigger toggle, Escape, Tab, and item activation.
Controlled and uncontrolled state
Use defaultOpen for uncontrolled menus. Use open when the open state comes from your app.
In Astro docs examples, open is best used as a static initial state example. Interactive controlled behavior is most useful in framework wrappers like React, Solid, Svelte, and Vue.
Disabled items
Pass disabled to Menu to disable the whole primitive, or to individual MenuItems to keep them in the list but prevent activation.
<div class="zui-menu" data-state="open">
<button class="zui-button zui-button-variant-outline zui-menu-trigger" aria-haspopup="menu" aria-expanded="true">Actions <i class="ph ph-caret-down"></i></button>
<div class="zui-menu-content" role="menu" data-state="open">
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem">Edit</button>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem" disabled>Archive</button>
<button class="zui-menu-item zui-button zui-button-variant-ghost" role="menuitem">Delete</button>
</div>
</div> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/react'
<Menu>
<MenuTrigger variant="outline">Actions <i className="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Edit</MenuItem>
<MenuItem disabled>Archive</MenuItem>
<MenuItem>Delete</MenuItem>
</MenuContent>
</Menu> ---
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/astro'
---
<Menu>
<MenuTrigger variant="outline">Actions <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Edit</MenuItem>
<MenuItem disabled>Archive</MenuItem>
<MenuItem>Delete</MenuItem>
</MenuContent>
</Menu> import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/solid'
<Menu>
<MenuTrigger variant="outline">Actions <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Edit</MenuItem>
<MenuItem disabled>Archive</MenuItem>
<MenuItem>Delete</MenuItem>
</MenuContent>
</Menu> <script lang="ts">
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/svelte'
</script>
<Menu>
<MenuTrigger variant="outline">Actions <i class="ph ph-caret-down"></i></MenuTrigger>
<MenuContent>
<MenuItem>Edit</MenuItem>
<MenuItem disabled>Archive</MenuItem>
<MenuItem>Delete</MenuItem>
</MenuContent>
</Menu> <template>
<Menu>
<MenuTrigger variant="outline">Actions <i class="ph ph-caret-down" /></MenuTrigger>
<MenuContent>
<MenuItem>Edit</MenuItem>
<MenuItem disabled>Archive</MenuItem>
<MenuItem>Delete</MenuItem>
</MenuContent>
</Menu>
</template>
<script setup>
import { Menu, MenuTrigger, MenuContent, MenuItem } from '@mrmartineau/zui/vue'
</script> Notes
- Menu is a strict action-menu primitive, not a generic popover panel.
MenuTriggeris always button-based.MenuItemrenders as a button by default, or as a link whenhrefis provided.- Disabled items are skipped by keyboard navigation and typeahead.
- Menu content stays mounted and is hidden with the
hiddenattribute when closed.
CSS custom properties
These custom properties are exposed on .zui-menu-content.
| Property | Default | Description |
|---|---|---|
--zui-menu-radius | var(--radius-xl) | Panel border radius |
--zui-menu-border | var(--color-border) | Panel border colour |
--zui-menu-bg | var(--color-surface) | Panel background |
--zui-menu-padding | var(--space-2xs) | Panel padding |