# Menu

> A list of actions or settings that opens from a button, with groups, check and radio items, and submenus.

- Name: `menu`
- Status: beta
- Categories: overlays
- File: the install writes `menu.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Other registry items: `@arbokui/glide-highlight`. The install adds them.
- Gallery: https://arbokui.com/components/menu
- Registry item: https://arbokui.com/r/menu.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/menu
   ```

## Use it for

- A set of actions for one thing, like Rename, Duplicate, and Delete for a file.
- One highlight glides from row to row as the pointer moves, and jumps when the arrow keys move it. Reduced motion makes it jump.
- A row of extra actions behind an icon button, when the page has no room for them.
- View settings that change the page at once: MenuCheckboxItem for on and off, MenuRadioGroup for one of a few choices.
- Use tone='danger' on an item that deletes something or cannot be undone.
- Put related items in a MenuGroup with a MenuGroupLabel, and split groups with MenuSeparator.
- A few links that go with the actions, like Settings or Billing. Use MenuLinkItem.
- The list opens at the end of the page body. In a box with its own theme, like a dark preview on a light page, pass that box as container on the top MenuPopup so the list keeps its colors. A container that clips its overflow can clip the list.

## Avoid it for

- A site's main navigation. Use links in a nav.
- Picking a value for a form field. Use a select.
- Fields or long text inside it. Use a popover.
- The only way to reach an action people use all the time. Show it as a button.
- Submenus more than one level deep. They are hard to use with a mouse and on phones.

## Props

- `Menu`: `{ open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, details) => void; modal?: boolean }`. Root. Holds the open state and renders no element. Takes open, defaultOpen, onOpenChange, and modal. modal is false by default, so a press outside closes the menu and still reaches the page, and one press moves from one open menu to the next. Set modal to block the page and its scroll while it is open.
- `open`: `boolean`. Menu. Whether it is open, controlled. Use it with onOpenChange.
- `defaultOpen`: `boolean`. Default `false`. Menu. Whether it is open at the start, when you do not control it.
- `onOpenChange`: `(open: boolean, details) => void`. Menu. Called with the new open state when it opens or closes.
- `modal`: `boolean`. Default `false`. Menu. Blocks the page and its scroll while it is open.
- `MenuTrigger`: `ComponentProps<"button"> & { render?: ReactElement }`. Part. The button that opens it. Pass render={\<Button />} to use your own button.
- `MenuPopup`: `ComponentProps<"div">`. Part. The list. It renders in a portal while open. Use it inside MenuSubmenu too.
- `MenuItem`: `ComponentProps<"div"> & { tone?: "neutral" | "danger"; disabled?: boolean; onClick?: () => void }`. Part. One action. Clicking it, or Enter or Space, runs onClick and closes the menu.
- `tone`: `"neutral" | "danger"`. Default `"neutral"`. MenuItem. danger colors the item for an action that deletes or cannot be undone.
- `disabled`: `boolean`. Default `false`. MenuItem, MenuCheckboxItem, MenuRadioItem, or MenuSubmenuTrigger. Dims the item, and it cannot be chosen.
- `MenuLinkItem`: `ComponentProps<"a"> & { closeOnClick?: boolean; label?: string }`. Part. A link, as an \<a>. Pass href, or render={\<Link href="/settings" />} for your router's link. It has no tone and no disabled state.
- `closeOnClick`: `boolean`. Default `true`. MenuLinkItem. Closes the menu when the link is clicked, so it does not stay open after a router changes the page.
- `MenuCheckboxItem`: `ComponentProps<"div"> & { checked?: boolean; defaultChecked?: boolean; onCheckedChange?: (checked: boolean) => void }`. Part. Turns a setting on and off, with a check mark. The menu stays open.
- `MenuRadioGroup`: `ComponentProps<"div"> & { value?: string; defaultValue?: string; onValueChange?: (value: string) => void }`. Part. Holds MenuRadioItems. Only one can be chosen.
- `MenuRadioItem`: `ComponentProps<"div"> & { value: string }`. Part. One choice in a MenuRadioGroup, with a dot when chosen. The menu stays open.
- `MenuGroup`: `ComponentProps<"div">`. Part. Groups related items.
- `MenuGroupLabel`: `ComponentProps<"div">`. Part. A small muted heading. Screen readers read it as the group's name.
- `MenuSeparator`: `ComponentProps<"div">`. Part. A line between groups. It is a top border, so a border color class recolors it.
- `MenuSubmenu`: `{ open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, details) => void }`. Root of a submenu, inside a MenuPopup. Holds a MenuSubmenuTrigger and a MenuPopup.
- `MenuSubmenuTrigger`: `ComponentProps<"div">`. Part. The item that opens a submenu, with an arrow at the end.
- `side`: `"top" | "right" | "bottom" | "left" | "inline-start" | "inline-end"`. Default `"bottom", or "inline-end" in a submenu`. MenuPopup. Which side of the trigger it opens on. It flips when there is no room.
- `align`: `"start" | "center" | "end"`. Default `"start"`. MenuPopup. How it lines up with the trigger. Use end for a trigger at the end of a row.
- `collisionPadding`: `number | { top?: number; right?: number; bottom?: number; left?: number }`. Default `8`. MenuPopup. Least gap between the list and the screen edge, in pixels. It keeps the list off the edge on phones.
- `container`: `HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null`. MenuPopup. The element the list opens in. By default, the end of the page body, so no parent can clip it. Pass an element, or a ref itself (not ref.current), to open it inside, for example to keep a parent's theme. Only client code has elements and refs. A null container shows no list. A submenu follows its parent menu there, so it needs no container of its own.
- `effect`: `"rise" | "grow"`. Default `"rise"`. MenuPopup. How the list opens and closes. rise fades in and moves out from the trigger. grow fades in and grows from the trigger, and closes the same way. Left out, it follows an effect-\<name> class on \<body>, where popups open, and rises with none. A submenu opens beside its menu, so give its MenuPopup the same effect.
- `className`: `string`. Any part. Extra classes. Later classes win, so className='min-w-56' on MenuPopup sets its width.

## Accessibility

- The trigger has aria-haspopup and aria-expanded. The list has role="menu", and items are menuitem, menuitemcheckbox, or menuitemradio.
- Enter, Space, or the down arrow on the trigger opens the menu and moves focus to the first item.
- Up and down arrows move between items, Home and End jump to the ends, and typing a letter jumps to the next item that starts with it.
- The right arrow opens a submenu, and the left arrow or Escape closes it. Escape closes the menu and puts focus back on the trigger.
- A disabled item is dimmed and has aria-disabled. Arrow keys still stop on it, so screen reader users know it is there, but it cannot be chosen.
- Check and radio items set aria-checked. The check mark and the dot are hidden from screen readers.
- A MenuLinkItem is an \<a> with role="menuitem". Arrow keys reach it like any item, and Enter follows the link.
- The item under keyboard focus gets the highlight and a focus ring.
- Danger items say what they do in words, like Delete, so the color is never the only sign.
- The theme tests hold item text and group labels to 4.5:1 on the popover color, and a highlighted item to 4.5:1 on its fill, in light and dark.
- The theme tests hold a danger item's text to 4.5:1 on the popover color and on its fill, and the focus ring to 3:1 on the popover color, in light and dark.

## Examples

### Actions for one file (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Menu>
  <MenuTrigger render={<Button emphasis="outline" />}>File</MenuTrigger>
  <MenuPopup>
    <MenuItem>Rename</MenuItem>
    <MenuItem>Duplicate</MenuItem>
    <MenuSeparator />
    <MenuItem tone="danger">Delete</MenuItem>
  </MenuPopup>
</Menu>
```

### View settings with check and radio items (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Menu>
  <MenuTrigger render={<Button emphasis="outline" />}>View</MenuTrigger>
  <MenuPopup>
    <MenuGroup>
      <MenuGroupLabel>Show</MenuGroupLabel>
      <MenuCheckboxItem defaultChecked>Toolbar</MenuCheckboxItem>
      <MenuCheckboxItem>Status bar</MenuCheckboxItem>
    </MenuGroup>
    <MenuSeparator />
    <MenuGroup>
      <MenuGroupLabel>Sort by</MenuGroupLabel>
      <MenuRadioGroup defaultValue="name">
        <MenuRadioItem value="name">Name</MenuRadioItem>
        <MenuRadioItem value="date">Date edited</MenuRadioItem>
      </MenuRadioGroup>
    </MenuGroup>
  </MenuPopup>
</Menu>
```

### A submenu (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Menu>
  <MenuTrigger render={<Button emphasis="outline" />}>Share</MenuTrigger>
  <MenuPopup>
    <MenuItem>Copy link</MenuItem>
    <MenuSubmenu>
      <MenuSubmenuTrigger>Send to</MenuSubmenuTrigger>
      <MenuPopup>
        <MenuItem>Email</MenuItem>
        <MenuItem>Text message</MenuItem>
      </MenuPopup>
    </MenuSubmenu>
  </MenuPopup>
</Menu>
```

### Links with a router (needs @arbokui/button)

```tsx
import Link from "next/link";

import { Button } from "@/components/ui/button";

<Menu>
  <MenuTrigger render={<Button emphasis="outline" />}>Account</MenuTrigger>
  <MenuPopup>
    <MenuLinkItem render={<Link href="/settings" />}>Settings</MenuLinkItem>
    <MenuLinkItem render={<Link href="/billing" />}>Billing</MenuLinkItem>
    <MenuSeparator />
    <MenuItem>Sign out</MenuItem>
  </MenuPopup>
</Menu>
```

### Run code when an item is chosen

```tsx
"use client";

function FileMenu({ onDelete }: { onDelete: () => void }) {
  return (
    <Menu>
      <MenuTrigger>File</MenuTrigger>
      <MenuPopup>
        <MenuItem tone="danger" onClick={onDelete}>
          Delete
        </MenuItem>
      </MenuPopup>
    </Menu>
  );
}
```

### Grows from its trigger (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Menu>
  <MenuTrigger render={<Button emphasis="outline" />}>Sort</MenuTrigger>
  <MenuPopup effect="grow">
    <MenuItem>Newest first</MenuItem>
    <MenuItem>Oldest first</MenuItem>
  </MenuPopup>
</Menu>
```
