Shim

Menu

Display a list of actions or options in a popover.

Documentation
React Aria
Pattern
W3C ARIA
Source
GitHub
Issues
Report
Composes
ListBox·Popover
Install
pnpm dlx @kkga/shim add menu
import { ListIcon } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { Menu, MenuItem, MenuSeparator, MenuTrigger } from "@/shim-ui/menu";

export default () => (
  <MenuTrigger>
    <Button aria-label="Menu">
      <ListIcon size={16} />
    </Button>
    <Menu>
      <MenuItem>Edit</MenuItem>
      <MenuItem>Duplicate</MenuItem>
      <MenuSeparator />
      <MenuItem intent="danger">Delete</MenuItem>
    </Menu>
  </MenuTrigger>
);

Install

Use the CLI or copy the source code manually.

pnpm dlx @kkga/shim add menu

Size

Set the overall menu dimensions with the size prop.

import { ListIcon } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { Menu, MenuItem, MenuSeparator, MenuTrigger } from "@/shim-ui/menu";

export default () =>
  ([1, 2, 3, 4] as const).map((size) => (
    <MenuTrigger key={size}>
      <Button aria-label="Menu" size={size}>
        <ListIcon size={"1em"} />
      </Button>
      <Menu size={size}>
        <MenuItem>Edit</MenuItem>
        <MenuItem>Duplicate</MenuItem>
        <MenuSeparator />
        <MenuItem intent="danger">Delete</MenuItem>
      </Menu>
    </MenuTrigger>
  ));

Sections

Group items with <MenuSection> and provide a heading via the title prop.

import { CaretDown } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { Menu, MenuItem, MenuSection, MenuTrigger } from "@/shim-ui/menu";

export default () => (
  <MenuTrigger>
    <Button aria-label="Pick file">
      Pick file
      <CaretDown size={12} weight="bold" />
    </Button>
    <Menu>
      <MenuSection title="Recent">
        <MenuItem>File 1</MenuItem>
        <MenuItem>File 2</MenuItem>
        <MenuItem>File 3</MenuItem>
      </MenuSection>
      <MenuSection title="Favorites">
        <MenuItem>File 4</MenuItem>
        <MenuItem>File 5</MenuItem>
        <MenuItem>File 6</MenuItem>
      </MenuSection>
    </Menu>
  </MenuTrigger>
);

Content

Menu follows the Collections API and supports both static and dynamic items.

Pass an items prop and a render function as children to render dynamic collections. The render function receives each item from items and should return a React element.

"use client";
import { CaretDownIcon } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { Menu, MenuItem, MenuTrigger } from "@/shim-ui/menu";

export default () => {
  const items = [
    { id: 1, name: "New" },
    { id: 2, name: "Open" },
    { id: 3, name: "Close" },
    { id: 4, name: "Save" },
    { id: 5, name: "Duplicate" },
    { id: 6, name: "Rename" },
    { id: 7, name: "Move" },
  ];

  return (
    <MenuTrigger>
      <Button aria-label="Menu">
        Options
        <CaretDownIcon size={12} weight="bold" />
      </Button>
      <Menu items={items}>{(item) => <MenuItem>{item.name}</MenuItem>}</Menu>
    </MenuTrigger>
  );
};

Selection

Menu supports single, multiple, and uncontrolled selection modes. Enable selection by setting the selectionMode prop.

"use client";
import { CaretDown } from "@phosphor-icons/react/dist/ssr";
import { useState } from "react";
import type { Selection } from "react-aria-components";
import { Button } from "@/shim-ui/button";
import { Menu, MenuItem, MenuTrigger } from "@/shim-ui/menu";

export default () => {
  const [align, setAlign] = useState<Selection>(new Set(["center"]));
  const [views, setViews] = useState<Selection>(new Set(["sidebar"]));

  return (
    <div className="flex gap-2">
      <MenuTrigger>
        <Button aria-label="Align">
          Align
          <CaretDown size={12} weight="bold" />
        </Button>
        <Menu
          onSelectionChange={setAlign}
          selectedKeys={align}
          selectionMode="single"
        >
          <MenuItem id="left">Left</MenuItem>
          <MenuItem id="center">Center</MenuItem>
          <MenuItem id="right">Right</MenuItem>
        </Menu>
      </MenuTrigger>

      <MenuTrigger>
        <Button aria-label="Views">
          Views
          <CaretDown size={12} weight="bold" />
        </Button>
        <Menu
          onSelectionChange={setViews}
          selectedKeys={views}
          selectionMode="multiple"
        >
          <MenuItem id="sidebar">Sidebar</MenuItem>
          <MenuItem id="toolbar">Toolbar</MenuItem>
          <MenuItem id="inspector">Inspector</MenuItem>
        </Menu>
      </MenuTrigger>
    </div>
  );
};