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>
<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>

Anatomy

Common props

MenuTrigger accepts the same visual props as a ZUI button:

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

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>

Notes

CSS custom properties

These custom properties are exposed on .zui-menu-content.

PropertyDefaultDescription
--zui-menu-radiusvar(--radius-xl)Panel border radius
--zui-menu-bordervar(--color-border)Panel border colour
--zui-menu-bgvar(--color-surface)Panel background
--zui-menu-paddingvar(--space-2xs)Panel padding

Theme

Copy this CSS to your project: