Skip to content
Browse docs

Menu

Beta

A 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 menu
pnpm dlx eris-ui@latest add menu
bunx eris-ui@latest add menu
yarn dlx eris-ui@latest add menu

Installs @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: nothing yet

---
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 loopFocus
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, closeOnSelect, loopFocus
MenuTrigger The button that opens it. Props: variant, size, aria-label
MenuContent The floating list
MenuItem An action or link. Props: value, href, disabled, variant, closeOnSelect
MenuCheckboxItem An on and off option. Props: value, checked, disabled, closeOnSelect
MenuRadioGroup A set of radio items. Props: label, name, value
MenuRadioItem One choice in a radio group. Props: value, disabled, closeOnSelect
MenuGroup A labelled section. Prop: label
MenuSeparator A dividing line