Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Variants and states

RestDisabled
Default
Default
  • Rest
  • Disabled

Menu styles hover, pressed and focus on its inner parts, which a still preview can't reach, so this table shows rest and disabled only.

Props

NameTypeDefaultDescription
trigger*ReactElement<unknown, JSXElementConstructor<any> | string>—The button that opens the menu, such as a Button or an IconButton, rendered in place of the menu. Required: it opens the menu, names it, and gets focus back when the menu closes.
childrenReactNode—The menu's items, in reading order: MenuItem, MenuLinkItem, MenuCheckboxItem, MenuRadioGroup, MenuGroup, MenuSeparator and MenuSubmenu.
openboolean—Whether the menu is open. Set it to control the menu, with onOpen and onClose to hear when it asks to change; leave it out for a menu its trigger opens.
defaultOpenbooleanfalseWhether the menu is open at first, when open is left out.
onOpen(event: Event, details: MenuRootChangeEventDetails) => void—Called when the menu asks to open, from its trigger: details.reason is 'trigger-press', 'trigger-hover' or another of Base UI's reasons, and details.cancel() keeps it closed.
onClose(event: Event, reason: Menu.Root.ChangeEventReason, details: Menu.Root.ChangeEventDetails) => void—Called when the menu asks to close: reason is 'item-press', 'escape-key', 'outside-press', 'focus-out' or another of Base UI's reasons, and details.cancel() keeps it open.
disabledbooleanfalseWhether the trigger ignores the pointer and the keys, so the menu can't open. The trigger carries data-disabled.
idstring—The popup's id, which the trigger's aria-controls reads. Base UI sets one itself when left out.
classNamestring—Reach the root, the popup. className is merged after pn-Menu.
styleCSSProperties—Reach the root, the popup. className is merged after pn-Menu.
refRef<HTMLDivElement>—Reach the root, the popup. className is merged after pn-Menu.
slotProps{ trigger?: Props<unknown> | undefined; portal?: MenuPortalProps | undefined; positioner?: MenuPositionerProps | undefined; }—Props for each part: trigger, portal, positioner.
trigger (required)ReactElement<unknown, JSXElementConstructor<any> | string>, default —
The button that opens the menu, such as a Button or an IconButton, rendered in place of the menu. Required: it opens the menu, names it, and gets focus back when the menu closes.
childrenReactNode, default —
The menu's items, in reading order: MenuItem, MenuLinkItem, MenuCheckboxItem, MenuRadioGroup, MenuGroup, MenuSeparator and MenuSubmenu.
openboolean, default —
Whether the menu is open. Set it to control the menu, with onOpen and onClose to hear when it asks to change; leave it out for a menu its trigger opens.
defaultOpenboolean, default false
Whether the menu is open at first, when open is left out.
onOpen(event: Event, details: MenuRootChangeEventDetails) => void, default —
Called when the menu asks to open, from its trigger: details.reason is 'trigger-press', 'trigger-hover' or another of Base UI's reasons, and details.cancel() keeps it closed.
onClose(event: Event, reason: Menu.Root.ChangeEventReason, details: Menu.Root.ChangeEventDetails) => void, default —
Called when the menu asks to close: reason is 'item-press', 'escape-key', 'outside-press', 'focus-out' or another of Base UI's reasons, and details.cancel() keeps it open.
disabledboolean, default false
Whether the trigger ignores the pointer and the keys, so the menu can't open. The trigger carries data-disabled.
idstring, default —
The popup's id, which the trigger's aria-controls reads. Base UI sets one itself when left out.
classNamestring, default —
Reach the root, the popup. className is merged after pn-Menu.
styleCSSProperties, default —
Reach the root, the popup. className is merged after pn-Menu.
refRef<HTMLDivElement>, default —
Reach the root, the popup. className is merged after pn-Menu.
slotProps{ trigger?: Props<unknown> | undefined; portal?: MenuPortalProps | undefined; positioner?: MenuPositionerProps | undefined; }, default —
Props for each part: trigger, portal, positioner.

Use it

import { Menu } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Menu" })

Keyboard and ARIA

From the Base UI reference for Menu and the installed MenuRoot, MenuTrigger, MenuPopup, useMenuItemCommonProps, MenuCheckboxItem, MenuRadioItem, MenuGroupLabel, MenuRadioGroup, MenuSubmenuTrigger and the list navigation they build on. "From" says who does it.

KeyDoesFrom
Enter, Space, ArrowDown on the triggerOpen the menu, calling onOpen with reason 'trigger-press' or 'list-navigation', and move focus to the first itemThe browser activates the button; Base UI opens and moves focus
ArrowUp on the triggerOpens the menu and moves focus to the last itemBase UI
ArrowDown, ArrowUpMove focus to the next and the previous item, disabled items included, skipping group labels and separators, and wrap from the last to the first and backBase UI
Home, EndMove focus to the first and the last itemBase UI
A letter or lettersMove focus to the next item whose text, or label, starts with themBase UI
Enter, Space on an itemRun its onClick and close the menu, calling onClose with reason 'item-press', and return focus to the trigger; on a disabled item, nothingBase UI
Enter, Space on a link itemFollow the linkThe browser; Base UI for Space
Enter, Space on a checkbox itemTurn it on or off, calling onChange with the new checked, and keep the menu openBase UI
Enter, Space on a radio itemChoose it, calling the group's onChange with its value, and keep the menu openBase UI
ArrowRight, Enter, Space on a submenu's rowOpen the submenu and move focus to its first itemBase UI
ArrowLeft in a submenuCloses the submenu and returns focus to its rowBase UI
EscapeCloses the innermost open menu, a submenu first, returning focus to its row or to the trigger; closing the menu calls onClose with reason 'escape-key'Base UI
Tab, Shift+TabMove focus out of the menu, on past the trigger or back to it, and close the menu, calling onClose with reason 'focus-out'Base UI

A press outside the popup closes the menu too, with reason 'outside-press', and focus returns to the trigger.

ElementRoleAccessible name fromStates announced
triggerbutton, native, or the caller's element's ownIts own content, from the calleraria-haspopup="menu", aria-expanded, from Base UI; disabled
rootmenu, from Base UIaria-labelledby the trigger, or the caller's aria-labelnone
itemmenuitem, from Base UIIts text, the caller's childrenaria-disabled while disabled
linkItemmenuitem, on a native <a href>, from Base UIIts text, the caller's childrennone
checkboxItemmenuitemcheckbox, from Base UIIts text, the caller's childrenaria-checked; aria-disabled while disabled
radioGroupgroup, from Base UIaria-labelledby its groupLabel, the caller's labelaria-disabled while disabled
radioItemmenuitemradio, from Base UIIts text, the caller's childrenaria-checked; aria-disabled while disabled
groupgroup, from Base UIaria-labelledby its groupLabel, the caller's labelnone
groupLabelnone: aria-hidden="true", from Base UI, read as the group's name insteadnonenone
separatorseparator, from Base UInonearia-orientation="horizontal"
submenumenuitem with aria-haspopup="menu", from Base UIIts text, the caller's labelaria-expanded while closed (Base UI drops it while open, for VoiceOver); aria-disabled while disabled
submenuPopupmenu, from Base UIaria-labelledby its rownone
startIcon, chevron, both indicatorsnone: aria-hidden="true"nonenone

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: each row is named by its label; the start icon, the chevron, the box and the tick are aria-hidden, and what they show is announced by the row's role and state: menuitemcheckbox with aria-checked for the box, menuitemradio with aria-checked for the tick, aria-haspopup="menu" for the chevron; a group's label is its name; disabled is aria-disabled.
  • Every landmark and named region has an accessible name: the menu is named by its trigger and a submenu by its row, through aria-labelledby, and every group with a label by it; the stories give every group a label but the Submenu story's radio group, whose choice the submenu's row, "Language", already names, as MenuRadioGroup's label row allows.
  • Every default label can be overridden: none here; Menu owns no strings.
  • Every shape the type accepts renders, in every variant: trigger, children, label and startIcon render a string, an element and a list alike; a long word in a label wraps (CR-11).
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; the closed menu renders nothing, and the unchecked box is a mark inside its row, aria-hidden like the checked one.
  • Native semantics first: a native <button> trigger and <a href> link items, and Base UI's menu, menuitem, menuitemcheckbox, menuitemradio, group and separator roles, with no ARIA of Perron's own.
  • Focus moves in reading order and returns on close: the arrow keys move through the items in document order, the order on screen; opening from the keys focuses the first item; closing, by any reason, returns focus to the trigger, also for a menu open at first render (CR-5), and closing a submenu returns it to the submenu's row.
  • Every target is at least size.target-min square: the smallest target is a row, at least size.target-min tall and as wide as the popup.
  • Loops stop when the system asks for less motion: none here; the menu has no loop and no transition (CR-12).

Owned strings

No owned strings: every word comes from the caller, as the trigger's content, each row's children or label, and each group's label. The box, the tick and the chevron say nothing of their own: the rows' roles and states announce them.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustPass the button that opens the menu as trigger, with words that name the menu, such as "Map options", or an IconButton with an aria-labelOpen a menu from a button placed beside it with open alone: the button loses aria-haspopup and aria-expanded, and the menu its nametypes
DD-2mustSet slotProps.trigger.nativeButton to false when trigger is not a <button>, as Base UI warnsPass a link as the trigger: a link goes somewhere, and a menu opens over the screenreview
DD-3mustDisable the menu with disabled on the MenuDisable only the trigger element: Base UI's trigger stays enabled and the menu still opensreview
DD-4mustPut actions in MenuItems, links in MenuLinkItems, settings in MenuCheckboxItems and one choice in a MenuRadioGroup with a labelPut a Button, a Switch or a plain <a> in the popup: it falls out of the arrow-key order and gets no menu rolereview
DD-5mustKeep a checkbox item's words the same on and off, such as "Show stop names", and let its box and aria-checked say the stateChange a setting's words when it turns on, or use a checkbox item for an actionreview
DD-6mustName each group with label, in sentence case, and divide groups with MenuSeparatorPut a heading or a styled <div> in the popup as a label: it isn't tied to its group, and the arrow keys would skip a group they can't namereview
DD-7shouldGive every row in a menu a start icon, drawn for icon.size.400, or give none of them one; a checkbox or radio item takes none, so a menu that holds one gives its other rows noneGive some rows an icon and leave others without, or use an icon drawn for icon.size.450: the labels start at two edges, and a scaled-down icon's stroke falls under 1.5 pxreview
DD-8shouldKeep a disabled item in place, so the person learns it exists, and keep menus short, a submenu one level deep at mostHide and show items as the state changes, or nest submenus: rows that move and deep paths are hard to follow on a phonereview
DD-9mustUse a menu for a short list of actions and settings that open from one buttonUse a menu to choose a form's value or to search a long list: that is a Select or a SearchField, which submit and filterreview
DD-10mustOpen a menu from a toolbar by passing a ToolbarButton as trigger, so it joins the toolbar's arrow-key orderPut a Menu's trigger beside a toolbar's buttons as a plain Button: it adds a tab stop and falls out of the toolbar's arrow keysreview
DD-11shouldLet the menu take z-index.dialog, its place against the trigger and its glassSet a z-index, a position or a background on the popup: it would fall under another overlay or leave the Popups familyreview