Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

Components / Overlays

Menu

A button that opens a list of commands: edit, copy, export, delete. Choosing one runs it and closes the menu. For picking a value that stays shown on the control, use Select.

Keyboard opens navigates and selects

When to useLink to "When to use"

  • Overflow actions for an item (a row's "more" button) or a toolbar.
  • Several related commands that don't each deserve a visible button.
  • View options with checkable items (kind: "checkbox" toggles, kind: "radio" picks one of a set).

Use something else when:

  • the user picks a value for a form: Select
  • there are only one or two actions: show Buttons
  • the panel holds controls or a form: Popover
  • the items navigate to pages: links in your navigation

UsageLink to "Usage"

tsx
Copy
import { IconButton, Menu } from "@arshad-shah/cynosure";
import { copy, edit, moreHorizontal, trash } from "@arshad-shah/cynosure/icons";

<Menu
  label="File actions"
  trigger={(props) => <IconButton {...props} icon={moreHorizontal} label="More actions" />}
  items={[
    { value: "edit", label: "Edit", icon: edit, shortcut: ["Ctrl", "E"] },
    { value: "copy", label: "Copy path", icon: copy },
    { label: "Danger zone", items: [{ value: "delete", label: "Delete", icon: trash, tone: "danger" }] },
  ]}
  onSelect={(value) => run(value)}
/>

trigger is a render prop: spread its props onto a Button or IconButton. Sections ({ label, items }) render as a named group after a separator.

Sections without a labelLink to "Sections without a label"

A section with no label ({ items: [...] }) is set apart by separator lines before and after it, without a group name: undo/redo, then cut/copy/paste.

Sections without alabel are separated

An item with items opens a submenu beside it instead of being chosen, nested to any depth. The entries are plain data, so a plugin can contribute them. onSelect receives the value chosen at any level, and choosing closes the whole menu.

tsx
Copy
items={[
  { value: "open", label: "Open" },
  {
    value: "move",
    label: "Move to",
    icon: folder,
    items: [
      { value: "inbox", label: "Inbox" },
      { value: "projects", label: "Projects", items: [{ value: "alpha", label: "Alpha" }] },
    ],
  },
]}

The submenu opens on the end side of its parent item, its first item level with the parent, and flips to the other side at the edge of the viewport.

Submenus open and close from the keyboard

Opening from elsewhereLink to "Opening from elsewhere"

open with onOpenChange controls the menu, so an app can open it from a keyboard shortcut; it opens on its first item. defaultOpen opens it on first render without controlling it.

Controlled open

Checkable itemsLink to "Checkable items"

Checkable items keep the menu open, so several options can be changed in one go. Keep checked in your state and update it in onSelect.

Checkable items stay open

Coloured iconsLink to "Coloured icons"

Item icons are muted by default. iconTone and iconColor colour one item's icon and mean what Icon's tone and color mean: iconTone is a semantic colour (accent, neutral, danger, success, warning, info, muted), iconColor is any CSS colour and overrides it (brand draws a brand mark in its owner's colour). The highlighted item keeps the colour and a disabled item shows its icon muted.

Item icons keep their colour

PropsLink to "Props"

Every prop of Menu
PropWhat it doesTypeDefault
triggerRequired
Render the trigger: (props) => <Button {...props}>Actions</Button>.(props: MenuTriggerProps) => ReactNode
labelRequired
Accessible name of the menu.string
itemsRequired
The commands: items (an item with items opens a submenu), or sections of items, labelled or not.MenuEntry[]
onSelectRequired
Called with the chosen item's value, from any level of submenu.(value: string) => void
open
Whether the menu is open, to open it from elsewhere (a keyboard shortcut). Pair with onOpenChange.boolean
defaultOpen
Open on first render, when open is not controlled. Default false.booleanfalse
onOpenChange
Called when the menu opens or closes: the trigger, Escape, a choice, Tab or a click outside.((open: boolean) => void)
placement
Side of the trigger the menu opens on. Default "bottom".Placement"bottom"
className
Added to the menu panel, for layout.string
style
Layout only, applied to the panel.CSSProperties

Generated from the type definitions in @arshad-shah/cynosure 0.5.0.

Item fields: value, label, icon, iconTone, iconColor, shortcut (keys such as ["Ctrl", "E"]), disabled, tone (neutral or danger), kind (checkbox or radio), checked, keepOpen and items (a submenu). Section fields: items and an optional label.

Behaviour and accessibilityLink to "Behaviour and accessibility"

Behaviour and accessibility, table 1
InputResult
Enter, Space, click or Down arrow arrow on the triggerOpens on the first item
Up arrow arrow on the triggerOpens on the last item
Down arrow / Up arrow arrows, Home, EndMove between items, wrapping and skipping disabled ones
A letterJumps to the next item starting with it
Enter, Space, clickRuns the item and closes the whole menu (checkable items stay open)
Right arrow arrow (Left arrow in right-to-left text), Enter, Space or click on a parent itemOpens its submenu on the first item
Left arrow arrow (Right arrow in right-to-left text) in a submenuCloses it, back to its parent item
Resting the pointer on a parent itemOpens its submenu after 150 ms, without moving focus
EscapeCloses one level: a submenu back to its parent item, the menu back to the trigger
TabCloses the whole menu and moves on to the next control
  • Roles: menu named by label, items as menuitem, menuitemcheckbox or menuitemradio with aria-checked, sections as named groups, unlabelled sections set apart by separators. A parent item has aria-haspopup="menu", aria-expanded and aria-controls naming its submenu, a menu named by the item's label.
  • Diagonal travel: while a submenu is open, another item must hold the pointer for 300 ms before the submenu closes or switches, so moving towards the submenu across other items keeps it open. Leaving the menu cancels a pending switch.
  • Shortcuts are shown with Kbd and announced through aria-keyshortcuts. The menu shows them; your app still has to handle the keys.
  • Disabled items stay visible (so people learn the command exists) but can't be focused or run.
  • Layers: inside a Popover or dialog, Escape closes only the menu.

TouchLink to "Touch"

Rows are at least 44px tall on coarse pointers, and a tap runs the item. Touch has no hover: a tap on a parent item opens its submenu.

ThemingLink to "Theming"

Tokens: --cy-menu-min-width, --cy-menu-max-width, --cy-menu-radius, --cy-menu-row-height and --cy-menu-offset (the gap from the trigger, also under a Menubar title).

ts
Copy
createTheme({ components: { menu: { minWidth: "240px", radius: "10px" } } });

Popover · Select · Toolbar · Kbd

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog