Skip to content

Menu

A list of actions that appears when a trigger is pressed. It can hold nested submenus and checkable rows, and is fully operable from the keyboard.

tsx
import { Button, Menu, MenuItem, MenuSeparator, MenuSubmenu } from 'neba';

<Menu trigger={<Button>Actions</Button>}>
  <MenuItem shortcut="⌘E">Rename</MenuItem>
  <MenuSubmenu label="Move to">
    <MenuItem>Archive</MenuItem>
  </MenuSubmenu>
  <MenuSeparator />
  <MenuItem color="danger">Delete</MenuItem>
</Menu>;

Rows are written as components rather than passed as an array, because each carries its own handler and icon and some of them are submenus. For a list that picks a value, use Select.

Props

PropTypeDefaultDescription
triggerReactElementThe element that opens the menu, wired up by Base UI. Not needed for a controlled menu opened elsewhere
openbooleanWhether it is open, for a controlled menu
defaultOpenbooleanfalseWhether it starts open
onOpenChange(open: boolean) => voidCalled when it opens or closes
sideshared'top' | 'right' | 'bottom' | 'left''bottom'Which edge of the trigger it hangs off. Flips when there is no room
alignshared'start' | 'center' | 'end''start'Where it sits along that edge
sideOffsetnumber6Distance from the trigger, in pixels
modalbooleantrueWhether the page behind is taken away while it is open
openOnHoverbooleanfalseOpens on hover too. For a menu bar, where crossing the row should walk through the others
loopFocusbooleantrueWhether the arrow keys wrap from the last row back to the first
disabledbooleanfalseUnavailable. The trigger stops opening anything
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and type scale
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'The rows’ padding only. A menu is as wide as its longest label, so it runs a tighter ladder than Box
childrenReactNodeThe rows: MenuItem, MenuGroup, MenuSeparator, MenuSubmenu and the rest
PropTypeDefaultDescription
onClick(event) => voidWhat the row does
hrefstringPassing it renders a real anchor. A menu of links has to be links, or none of them opens in a new tab
targetstringWhere the link opens. Ignored without href. Anything other than this tab also gets noopener noreferrer added to rel
relstringThe link's rel. Merged rather than replaced, so writing nofollow does not take the protection off a link that still opens elsewhere
startIconReactNodeContent before the label
endIconReactNodeContent after the label, before any shortcut
shortcutReactNodeThe keystroke that does the same thing, set muted at the end. Shown, never bound
descriptionReactNodeA second line under the label
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'Re-points this row's colour family: danger for the one that deletes. Defaults to the menu's own
closeOnClickbooleantrueWhether picking the row closes the menu
disabledbooleanfalseUnavailable. Still listed, and still found by typeahead
labelstringWhat typeahead matches against, when the label is not a plain string
childrenReactNodeThe label
PropTypeDefaultDescription
labelReactNodeThe label on the row that opens it
startIconReactNodeContent before the label
sideshared'top' | 'right' | 'bottom' | 'left''right'Which edge of the parent row it opens against
sideOffsetnumber4Distance from the parent menu, in pixels
disabledbooleanfalseUnavailable
childrenReactNodeThe nested rows: one of which may be another MenuSubmenu, to any depth

ContextMenu

PropTypeDefaultDescription
contentReactNodeThe rows, written exactly as they are inside a Menu
children * ReactNodeThe area that answers a right-click or a long press
openbooleanWhether it is open
defaultOpenbooleanfalseWhether it starts open
onOpenChange(open: boolean) => voidCalled when it opens or closes
disabledbooleanStops it answering a right-click or a long press
loopFocusbooleantrueWhether the arrow keys wrap from the last row back to the first
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and type scale
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'The rows’ padding only

Examples

href · startIcon · shortcut

href renders the row as a real <a>, so it can be opened in a new tab or have its address copied. shortcut takes a Shortcut. MenuSeparator divides rows into groups.

There is no depth limit: a submenu's children are menu rows too, so one of them can be another MenuSubmenu. They open on hover, and moving the pointer diagonally into an open submenu does not close it.

Checkable and radio rows

A tick is for items that can be on together; a dot is for one-of-a-set. Neither closes the menu when picked. closeOnClick can be set per row.

ContextMenu

A menu opened by right-click. Pass the rows as content and the target area as children.

size and density

side · align · openOnHover

side and align place the popup relative to the trigger. openOnHover opens the menu without a click.

Accessibility

  • The menu / menuitem roles, roving focus with the arrow keys, Home and End, typeahead, Escape, closing on an outside click and restoring focus to the trigger are all handled.
  • Give a destructive row color="danger"; the text, the soft background and the focus ring turn over together.
  • When the label is not a string, give label the text typeahead should match against.
  • A disabled row stays listed and findable by typeahead. A row that disappears reads as "there is no such thing" rather than "it is not available here".

Released under the MIT License