Menu
Display a list of actions or options in a popover.
- Documentation
- React Aria
- Pattern
- W3C ARIA
- Source
- GitHub
- Issues
- Report
Menu example
import { DotsThreeVerticalIcon } 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">
<DotsThreeVerticalIcon size={16} weight="bold" />
</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.
Command
Source code
pnpm dlx @kkga/shim add menu
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 fromitems
and should return a React element.
Content example
"use client";
import { CaretDown } 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
<CaretDown 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. The default is none
.
Selection example
"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>
);
};
Size
Set the overall menu dimensions with the size
prop.
Size example
import { DotsThreeVerticalIcon } 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" size={1}>
<DotsThreeVerticalIcon size={16} weight="bold" />
</Button>
<Menu size={1}>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
<MenuTrigger>
<Button aria-label="Menu" size={2}>
<DotsThreeVerticalIcon size={16} weight="bold" />
</Button>
<Menu size={2}>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
<MenuTrigger>
<Button aria-label="Menu" size={3}>
<DotsThreeVerticalIcon size={20} weight="bold" />
</Button>
<Menu size={3}>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
<MenuTrigger>
<Button aria-label="Menu" size={4}>
<DotsThreeVerticalIcon size={24} weight="bold" />
</Button>
<Menu size={4}>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
</>
);
Sections
Group items with <MenuSection>
and provide a heading via thetitle
prop.
Sections example
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>
);
Submenu
Create nested menus with <SubmenuTrigger>
. Pass a trigger element followed by a submenu rendered with <Menu>
.
Submenu example
import { DotsThreeVertical } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import {
Menu,
MenuItem,
MenuSeparator,
MenuTrigger,
SubmenuTrigger,
} from "@/shim-ui/menu";
export default () => (
<MenuTrigger>
<Button aria-label="Menu">
<DotsThreeVertical size={16} weight="bold" />
</Button>
<Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<SubmenuTrigger>
<MenuItem>Move...</MenuItem>
<Menu aria-label="Move">
<SubmenuTrigger>
<MenuItem>Folder...</MenuItem>
<Menu aria-label="Folder">
<MenuItem>Folder 1</MenuItem>
<MenuItem>Folder 2</MenuItem>
<MenuItem>Folder 3</MenuItem>
</Menu>
</SubmenuTrigger>
<SubmenuTrigger>
<MenuItem>Workspace...</MenuItem>
<Menu aria-label="Workspace">
<MenuItem>Workspace 1</MenuItem>
<MenuItem>Workspace 2</MenuItem>
<MenuItem>Workspace 3</MenuItem>
</Menu>
</SubmenuTrigger>
</Menu>
</SubmenuTrigger>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
);