MenuItem

A row of a list, in one of two roles:

Demos

For usage examples, visit the component demo page: MenuItem

Import

import { MenuItem } from '@soroush.tech/design-system/MenuItem'

Props

PropTypeDefaultDescription
valuestring | number-The value this row represents - reported to Select's onChange or MenuList's onSelect. Required inside a Select.
childrenReactNode-The row label.
asElementType'li'The element used for the root node.
disabledbooleanfalseDisables the row - it cannot be highlighted or chosen. In a menu the keys skip it.
selectedbooleanfalseMarks the row as the current selection. Injected by Select; in a menu it sets aria-checked on menuitemradio / menuitemcheckbox rows and ticks them.
highlightedbooleanfalseMarks the option as the keyboard-highlighted row. Injected by Select.
multiplebooleanfalseReserves a leading checkmark slot for multi-select menus. Injected by Select.
colorkeyof Theme['palette']'primary'Accent color for hover/selected shading - resolves to theme.palette[color].
textColorkeyof Theme['text']color's mainBase text color of the row - resolves against theme.text. Falls back to the accent color's main.
sizekeyof Theme['sizes']'md'Density token - resolves against theme.sizes.
densebooleanfalseCompact vertical padding, independent of size.
disableGuttersbooleanfalseRemove the left and right padding.
dividerbooleanfalseAdd a 1px bottom border to separate the row.
autoFocusbooleanfalseFocus the row on first mount, and whenever autoFocus flips from false to true.
focusVisibleClassNamestring-Class applied only while the row has keyboard focus - a :focus-visible hook.
onSelect(value: string | number) => void-Fired with value when the option is chosen. Injected by Select.
idstring-DOM id. Select assigns one so the trigger can reference it via aria-activedescendant.

Used inside a MenuList; ignored by Select. color, textColor, size and dense default to the MenuList's own.

PropTypeDefaultDescription
role'menuitem' | 'menuitemradio' | 'menuitemcheckbox''menuitem'A command, the current choice of a group, or an on/off setting.
startIconReactNode-An icon in the leading column. Every row keeps the column, so labels line up; a checked row shows a tick there.
shortcutReactNode-The row's keyboard shortcut, shown quieter at its end and kept out of its name. A string is also given to the row as aria-keyshortcuts.
aria-keyshortcutsstring-What a screen reader hears of the row's keys - the aria-keyshortcuts form, such as Control+= - where it differs from what shortcut shows (Ctrl+=). It wins over a string shortcut.
notestring-Why the row is off - shown by a Tooltip beside the row (it takes no click, and opens over a disabled row) and the row's accessible description, open or closed. Never a native title.
onClick(event) => void-Fired when the row is chosen, by a click, Enter or Space.
submenuReactNode-The rows of a submenu this row opens, beside it. null or [] is a submenu with nothing in it yet. A row with a submenu opens it rather than being chosen.
emptyLabelReactNode-Shown as one disabled row in an empty submenu ("Coming soon"); without it, the submenu stays empty.
secondaryActionReactNode-A control of the row's own beside it - a close button. It sits with the row in its role="none" entry, out of the arrow keys and hidden from assistive technology (a menu owns rows alone): give it tabIndex={-1}, and the row a key of its own through MenuList's onKeyDown.
shouldAnnounceSecondaryActionbooleanfalseLeaves secondaryAction in the accessibility tree - a screen reader finds the control by its own name. A menu owns rows alone, so a control beside a row is an exception a tool may report; name it and keep it out of the tab order.
<MenuItem
  submenu={[
    <MenuItem key="main" value="main" role="menuitemradio" selected>
      main
    </MenuItem>,
    <MenuItem key="feat" value="feat/menu" role="menuitemradio">
      feat/menu
    </MenuItem>,
  ]}
>
  Local branches
</MenuItem>
<MenuItem submenu={null} emptyLabel="Coming soon">
  Remote branches
</MenuItem>

A row can't contain rows, so the row and its submenu sit side by side in a role="none" entry; the row is aria-haspopup="menu" with aria-expanded, and shows a chevron that mirrors right to left.

  • Focus decides the submenu. A row opens its submenu when it takes focus - by the keys or by the pointer, which moves focus as it goes - so it is already open when Right is pressed. Focus moving to a sibling row closes it.
  • Keys. Right, Enter or Space go into the submenu's first enabled row; Left or Escape close it and come back to the row. Right to left, Left and Right swap. Each list moves only between its own rows.
  • Choosing a row anywhere in a submenu is the outer list's choice (onSelect) and, in a Menu, closes the whole menu.
  • Placement. Beside the row, on the side away from the menu's align edge (to the right of a start-aligned menu, left to right), or the other side when the window has no room - and a submenu that opened the other way takes its own submenus that way too, so they go on away from the menu rather than back over it. With room on neither side - a phone-width window - it opens under the row instead, or over it near the bottom, so it never covers the row it came from.

Rows from data

Rows can come as data instead - see MenuItems.

Notes

  • color and textColor are supplied by Select (from its own props), but an item's own value wins - set them on a MenuItem to override the row's accent or text color.
  • Hover and selected shading are derived from theme.palette[color] with hex-suffix opacity, so the row stays on-theme in both color schemes.
  • mousedown is prevented so clicking an option does not blur Select's trigger - the trigger keeps focus for its aria-activedescendant model. A menu row takes focus itself instead, and the pointer moves focus as it goes, so one row is highlighted at a time.
  • A disabled menu row keeps pointer events, so its note tooltip shows on hover; the row sits in an entry (role="none") with the description the tooltip keeps for it.

Source code

If this page does not answer your question, have a look at the implementation of the component for more detail.