Menu
This is an accessible menu of actions that opens against a trigger. It obeys the WAI-ARIA menu button pattern: the panel has role="menu" and its children have role="menuitem". The arrow keys and the typeahead move the focus, and a menu can contain a submenu.
Last action: —
Anatomy
Menu.Root shares the open state and the reference to the trigger. Menu.Content makes the role="menu" panel in a portal, at a position against the trigger. The default placement is bottom-start. Each item accepts an onAction function, and you can disable an item.
<script>
import { Menu } from '@human-kit/ui';
</script>
<Menu.Root>
<Menu.Trigger>Options</Menu.Trigger>
<Menu.Content>
<Menu.Item onAction={edit}>Edit</Menu.Item>
<Menu.Item disabled>Duplicate</Menu.Item>
<Menu.Separator />
<Menu.Group>
<Menu.GroupLabel>Share</Menu.GroupLabel>
<Menu.Item onAction={copyLink}>Copy link</Menu.Item>
</Menu.Group>
</Menu.Content>
</Menu.Root>Groups and separators
Put the items that go together in a Menu.Group with a Menu.GroupLabel. The group has role="group", and the label gives it its name. Use Menu.Separator to divide the sections.
Submenus
Put a Menu.SubmenuRoot in the menu. It contains a Menu.SubmenuTrigger and its own Menu.Content. The default placement is right-start. A submenu uses a safe triangle for the pointer. While the pointer moves at an angle to the open submenu, the items that the pointer goes over do not close it.
Context menu
Use Menu.ContextTrigger in place of Menu.Trigger to open the menu from a surface, not from a button. All of the parts below the trigger stay the same: the items, the groups, the submenus, and the keyboard operation.
Last action: —
Menu.ContextTrigger makes a plain element, not a button. Thus it can contain any content. It opens in three ways, and the position of the panel is different in each way:
- A right click — at the pointer, against it. For a context menu,
Menu.Contentmakes its defaultoffset0, and the panel opens down and to the right, like a native menu. A second right click moves the open menu to the new position. It does not close the menu and open it again. - A long press with a finger or a pen — at the finger. This is how the menu opens on a telephone, where the
contextmenuevent is not dependable. The surface sets-webkit-touch-callout: none; user-select: nonein its style attribute. Thus iOS shows the menu and not the text callout. To stop this, setpreventTouchCallout={false}. To stop the long press, setlongPress={false}. - The
Shift+F10keys or theContextMenukey — against the surface, with the focus on the first item. There is no pointer. If the menu opened at the last position of the pointer, the panel would go to a position that the keyboard user did not select.
A left press at any position, and also on the surface, closes the menu. This is the behavior of a native menu.
In a list where each row has the same menu, give each row its own Menu.Root. The state belongs to the root. Thus the menu belongs to the row that the user clicked.
Usage guidelines
- Use
Menu.Rootto share the open state and the reference to the trigger. PutMenu.TriggerandMenu.Contentin it. - Use
Menu.Itemfor an action. Give it anonActionfunction. You can also give itdisabled,closeOnSelect, ortextValuefor the typeahead. - The
closeOnSelectprop controls the menu after the user activates an item. Set it onMenu.Root, or on oneMenu.Item. The default istrue, and the menu closes. - The
loopprop moves the focus from the last item to the first item. The default istrue. Thetypeaheadprop moves the focus to an item by the text that the user types. The default istrue. - Put the items that go together in a
Menu.Groupwith aMenu.GroupLabel. UseMenu.Separatorto divide the sections. - The
onOpenChange(open, details)function reports the cause of the change indetails.reason. Calldetails.cancel()to stop the change.
Accessibility
Menu.Triggeris a button witharia-haspopup="menu"andaria-expanded. TheArrowDownkey, theEnterkey, and theSpacekey open the menu and move the focus to the first item. TheArrowUpkey opens the menu and moves the focus to the last item.Menu.Contenthasrole="menu", and each item hasrole="menuitem". The arrow keys move the focus. The typeahead moves the focus to an item by the text that the user types.- The
Escapekey closes the top menu and moves the focus back to its trigger. TheTabkey and an interaction outside the menu close all of the menus. - In a submenu, the
ArrowLeftkey closes that submenu only. On a submenu trigger, theArrowRightkey opens the submenu. - By default,
Menu.ContextTriggeris a tab stop (tabindex={0}). Thus a keyboard user can go to it and pushShift+F10. Settabindex={-1}when the trigger is in a component that already controls the focus with a roving tabindex, for example a table or a tree. - The surface has
aria-keyshortcuts="Shift+F10"and no more. Thearia-haspopupandaria-expandedproperties are not global ARIA properties, thus they are not valid on a generic element. This is a true limit. A context menu must never be the only route to an action. Give the sameMenu.RootaMenu.Triggerthat the user sees, or put the same actions in a different part of the page.
API reference
Root
The container with the state. It shares the open state, the reference to the trigger, and the settings (loop, typeahead, closeOnSelect) with all of the parts.
* required. Native HTML attributes of the underlying element are also accepted.
Trigger
The button that opens the menu, with aria-haspopup="menu" and aria-expanded. The ArrowDown, Enter, and Space keys open the menu and move the focus to the first item. The ArrowUp key opens it and moves the focus to the last item.
* required. Native HTML attributes of the underlying element are also accepted.
ContextTrigger
A surface that opens the menu at the pointer, from a right click, a long press, or the Shift+F10 keys. It makes a plain element and not a button, thus it can contain any content.
* required. Native HTML attributes of the underlying element are also accepted.
Content
The role="menu" panel that floats. It goes in a portal and takes its position against the trigger. A submenu uses it as its panel.
* required. Native HTML attributes of the underlying element are also accepted.
Overlay
An optional backdrop behind the menu. Put it beside Menu.Content, in Menu.Root. In a chain of submenus, only the root menu must have one.
* required. Native HTML attributes of the underlying element are also accepted.
Item
An action, with role="menuitem". It accepts `onAction`, `disabled`, `closeOnSelect` (which replaces the default of the Root), `textValue` for the typeahead, and an optional stable `value`.
* required. Native HTML attributes of the underlying element are also accepted.
Separator
A line between the menu items, with role="separator".
* required. Native HTML attributes of the underlying element are also accepted.
Group
Contains the items that go together, with role="group". An optional Menu.GroupLabel gives it its name, through aria-labelledby.
* required. Native HTML attributes of the underlying element are also accepted.
GroupLabel
The accessible name of a Menu.Group. The group points at it with aria-labelledby.
* required. Native HTML attributes of the underlying element are also accepted.
SubmenuRoot
The container with the state of a submenu. Put it in a Menu.Content, with a Menu.SubmenuTrigger and its own Menu.Content. The settings come from the menu above it.
* required. Native HTML attributes of the underlying element are also accepted.
SubmenuTrigger
An element with role="menuitem" that opens its submenu. The pointer, the ArrowRight key, the Enter key, and the Space key open it. It is also an item of the menu above it.
* required. Native HTML attributes of the underlying element are also accepted.