Navigation
Menu
A compact list of actions opened by a button.
When to use it: Use it for actions, not choosing a persistent form value.
Example
import {
Menu,
MenuItem,
MenuList,
MenuPopover,
MenuSection,
MenuSeparator,
MenuTrigger,
} from "@comp0/react";
import { ChevronRightIcon } from "@heroicons/react/16/solid";
export function Example() {
return (
<Menu>
<MenuTrigger className="rounded border border-zinc-950/10 px-3 py-2.5 text-base text-zinc-800 select-none sm:py-2 sm:text-sm dark:border-white/10 dark:text-zinc-100">
Actions
</MenuTrigger>
<MenuPopover
placement="bottom start"
offset={8}
className="w-44 rounded border-0 bg-white p-1 shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-900 dark:shadow-none dark:ring-white/10"
>
<MenuList>
<MenuSection aria-label="Document" className="grid">
<MenuItem
className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
value="rename"
>
Rename
</MenuItem>
<MenuItem
className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
value="duplicate"
>
Duplicate
</MenuItem>
</MenuSection>
<MenuSeparator className="my-1 h-px bg-zinc-950/10 dark:bg-white/10" />
<Menu>
<MenuTrigger className="flex w-full items-center justify-between rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800">
Share to <ChevronRightIcon className="size-4 text-zinc-400" aria-hidden="true" />
</MenuTrigger>
<MenuPopover
placement="right top"
offset={4}
className="w-40 rounded border-0 bg-white p-1 shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-900 dark:shadow-none dark:ring-white/10"
>
<MenuList>
<MenuItem
className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
value="email"
>
Email
</MenuItem>
<MenuItem
className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
value="copy-link"
>
Copy link
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
</MenuList>
</MenuPopover>
</Menu>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
Menu
Open-state provider; nest a whole Menu inside a MenuPopover to create a submenu. Does not add a DOM element.
MenuTrigger
Button that opens the menu; inside a parent menu it becomes the submenu item. Owns a DOM element.
MenuPopover
Role-less floating surface around the menu. Owns a DOM element.
MenuListOptional
Menu collection and keyboard interaction boundary. Owns a DOM element.
MenuSectionOptional
Optional labelled section. Owns a DOM element.
MenuSeparatorOptional
Rule between groups of items. Owns a DOM element.
MenuItem
Action item. Owns a DOM element.
Step by step
- 1
Add the main part
Start Menu with MenuTrigger.
- 2
Add the supporting parts
Place MenuPopover after the trigger, then put MenuItem children inside MenuList.
- 3
Make the behavior clear
Give MenuList an aria-label when its purpose is not obvious.
Exampletsx <Menu><MenuTrigger>Actions</MenuTrigger><MenuPopover><MenuList aria-label="Actions"><MenuItem>Archive</MenuItem></MenuList></MenuPopover></Menu>
Keyboard
- ↓
- Opens from the trigger, or moves to the next item.
- ↑
- Opens from the trigger, or moves to the previous item.
- →
- Opens the focused submenu item. · submenu
- ←
- Closes the submenu and refocuses its item. · submenu
- ↵
- Activates item.
- Esc
- Closes and returns focus to trigger.
- ⇥
- Closes the menu and moves on.
Forms and accessibility
No native form behavior.
Accessibility checklist
- Give MenuList an aria-label when trigger text is vague.
- Use MenuItem for actions, not a form selection.
- Return focus to the trigger when the menu closes.
API reference
import { Menu, MenuPopover, MenuList, MenuItem, MenuSection, MenuSeparator, MenuTrigger } from "@comp0/react";Menu
Context onlyOpen-state provider; nest a whole Menu inside a MenuPopover to create a submenu.
| Prop | Type | Description |
|---|---|---|
open / defaultOpen | boolean | Controlled or initial open state. |
onToggle | (open: boolean) => void | Receives the next open state. |
MenuTrigger
DOM elementButton that opens the menu; inside a parent menu it becomes the submenu item.
| Prop | Type | Description |
|---|---|---|
disabled | boolean | Disables opening. |
as | ElementType | Fragment | Fragment merges the trigger onto your own element child. |
MenuPopover
DOM elementRole-less floating surface around the menu.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Trigger side to open on, such as "bottom start" or "right top" for submenus; flips when there is no room. |
offset | number | Pixel gap between the trigger and the menu. |
MenuList
OptionalDOM elementMenu collection and keyboard interaction boundary.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Names the menu when the trigger text is vague. |
id | string | Overrides the collection id targeted by the trigger. |
MenuSection
OptionalDOM elementOptional labelled section.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Names the group of items. |
MenuSeparator
OptionalDOM elementRule between groups of items.
MenuItem
DOM elementAction item.
| Prop | Type | Description |
|---|---|---|
onClick | (event) => void | Runs the action; preventDefault keeps the menu open. |
value | string | Optional identity for typeahead and data-value. |
disabled | boolean | Disables the action. |
textValue | string | Overrides the text crawled from children when markup makes it ambiguous. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
[data-open]on MenuTrigger, MenuPopover
- The menu is open.
:popover-openon MenuPopover
- Native pseudo-class equivalent.
:focus-visibleon MenuItem
- The item has visible keyboard focus.
[data-disabled]on MenuItem
- The item is disabled.