Menu
BetaA dropdown list of actions, links and options.
---
import Menu from "@/components/ui/menu/Menu.astro";
import MenuTrigger from "@/components/ui/menu/MenuTrigger.astro";
import MenuContent from "@/components/ui/menu/MenuContent.astro";
import MenuItem from "@/components/ui/menu/MenuItem.astro";
import MenuSeparator from "@/components/ui/menu/MenuSeparator.astro";
---
<Menu>
<MenuTrigger>Actions</MenuTrigger>
<MenuContent>
<MenuItem value="edit">Edit</MenuItem>
<MenuItem value="duplicate">Duplicate</MenuItem>
<MenuItem value="archive" disabled>
Archive
</MenuItem>
<MenuItem href="/docs">Open the docs</MenuItem>
<MenuSeparator />
<MenuItem value="delete" variant="danger">
Delete
</MenuItem>
</MenuContent>
</Menu>Installation
npx eris-ui@latest add menupnpm dlx eris-ui@latest add menubunx eris-ui@latest add menuyarn dlx eris-ui@latest add menuInstalls @zag-js/menu, @zag-js/vanilla. Also adds button.
Items
A MenuItem is an action. Give it an href and it becomes a link that works with a click, with Enter, and with a middle click to open in a new tab. variant="danger" marks destructive actions, and disabled items are skipped by the arrow keys.
Reacting to a choice
Choosing an item fires an eris:select event on it. The event bubbles, so one listener on the menu covers every item. event.detail.value is the item’s value.
Chosen:
---
import Menu from "@/components/ui/menu/Menu.astro";
import MenuTrigger from "@/components/ui/menu/MenuTrigger.astro";
import MenuContent from "@/components/ui/menu/MenuContent.astro";
import MenuItem from "@/components/ui/menu/MenuItem.astro";
---
<div class="flex flex-col items-center gap-4">
<Menu id="demo-events">
<MenuTrigger>Sort by</MenuTrigger>
<MenuContent>
<MenuItem value="name">Name</MenuItem>
<MenuItem value="date">Date modified</MenuItem>
<MenuItem value="size">Size</MenuItem>
</MenuContent>
</Menu>
<p class="text-sm text-muted">
Chosen: <output id="demo-events-output">nothing yet</output>
</p>
</div>
<script>
import type { MenuSelectDetail } from "@/components/ui/menu/menu";
document.addEventListener("astro:page-load", () => {
const menu = document.querySelector('[data-eris-id="demo-events"]');
const output = document.querySelector("#demo-events-output");
menu?.addEventListener("eris:select", (event) => {
const { value } = (event as CustomEvent<MenuSelectDetail>).detail;
if (output) output.textContent = value;
});
});
</script>Checkbox and radio items
A MenuCheckboxItem toggles on and off. MenuRadioItems inside a MenuRadioGroup let the user pick one; set the group’s value to the item chosen on load.
Their eris:select event also carries checked, and for a radio item group, the name of its group.
By default the menu closes after any choice. Here closeOnSelect={false} keeps it open so several options can be changed. You can also set closeOnSelect on a single item.
---
import Menu from "@/components/ui/menu/Menu.astro";
import MenuTrigger from "@/components/ui/menu/MenuTrigger.astro";
import MenuContent from "@/components/ui/menu/MenuContent.astro";
import MenuCheckboxItem from "@/components/ui/menu/MenuCheckboxItem.astro";
import MenuRadioGroup from "@/components/ui/menu/MenuRadioGroup.astro";
import MenuRadioItem from "@/components/ui/menu/MenuRadioItem.astro";
import MenuGroup from "@/components/ui/menu/MenuGroup.astro";
import MenuSeparator from "@/components/ui/menu/MenuSeparator.astro";
---
<Menu closeOnSelect={false}>
<MenuTrigger>View</MenuTrigger>
<MenuContent>
<MenuGroup label="Show">
<MenuCheckboxItem value="toolbar" checked>
Toolbar
</MenuCheckboxItem>
<MenuCheckboxItem value="sidebar">Sidebar</MenuCheckboxItem>
</MenuGroup>
<MenuSeparator />
<MenuRadioGroup label="Density" name="density" value="comfortable">
<MenuRadioItem value="compact">Compact</MenuRadioItem>
<MenuRadioItem value="comfortable">Comfortable</MenuRadioItem>
<MenuRadioItem value="spacious">Spacious</MenuRadioItem>
</MenuRadioGroup>
</MenuContent>
</Menu>Groups and separators
MenuGroup and MenuRadioGroup take a label, shown above their items and read out as the group’s name. MenuSeparator draws a line between sections.
Icon trigger
A trigger that shows only an icon needs an aria-label. A Tooltip can wrap the trigger to show the same hint to everyone else.
---
import Menu from "@/components/ui/menu/Menu.astro";
import MenuTrigger from "@/components/ui/menu/MenuTrigger.astro";
import MenuContent from "@/components/ui/menu/MenuContent.astro";
import MenuItem from "@/components/ui/menu/MenuItem.astro";
import Tooltip from "@/components/ui/tooltip/Tooltip.astro";
---
<Menu placement="bottom-end">
<Tooltip text="More options">
<MenuTrigger size="icon" aria-label="More">
<svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor">
<circle cx="3" cy="8" r="1.25"></circle>
<circle cx="8" cy="8" r="1.25"></circle>
<circle cx="13" cy="8" r="1.25"></circle>
</svg>
</MenuTrigger>
</Tooltip>
<MenuContent>
<MenuItem value="rename">Rename</MenuItem>
<MenuItem value="share">Share</MenuItem>
</MenuContent>
</Menu>Placement
placement and gutter work as on Popover. The default is bottom-start, 6 pixels from the trigger. A menu taller than the space available scrolls.
Keyboard
| Key | Does |
|---|---|
| Enter, Space or ↓ on trigger | Opens the menu on its first item |
| ↑ on the trigger | Opens the menu on its last item |
| ↓ / ↑ | Next or previous item. Wraps around with loop |
| Home / End | First or last item |
| Letters | Jumps to the next item that starts with what you type |
| Enter or Space | Chooses the item |
| Esc | Closes the menu and returns focus to the trigger |
Tab does not leave an open menu. Submenus and right-click context menus are not part of this component yet.
Parts
| Component | Renders |
|---|---|
Menu |
The root. Props: id, placement, gutter, close, loop |
Menu |
The button that opens it. Props: variant, size, aria-label |
Menu |
The floating list |
Menu |
An action or link. Props: value, href, disabled, variant, close |
Menu |
An on and off option. Props: value, checked, disabled, close |
Menu |
A set of radio items. Props: label, name, value |
Menu |
One choice in a radio group. Props: value, disabled, close |
Menu |
A labelled section. Prop: label |
Menu |
A dividing line |