Skip to content

Menu

Menu is a list of groupable actions inside of a container. It is toggled by the user and is most often used when space is limited.
Figma logo
Anatomy of a Menu
Menu and its sub-components
  • MenuItemLink and MenuItem have the same styling and keyboard events
  • Menu items can be grouped to separate categories
  • Icons are optional but should be applied to all items within the Menu when used
  • Icons are always aligned to the left of the label
  • Use a MenuGroup to separate significant actions or destructive actions from less consequential actions. E.g. actions that alter user data or costs should be separated from actions that link to another page
  • A label is required when using a group
  • Use a MenuSeparator to help visually differentiate between groups
Menu in a small viewport
Menu is pinned to the bottom of small viewports
  • Menu is pinned to the bottom of the viewport and covers 100% of the screen width on screens narrower than 600px
  • On short screens, the content within Menu becomes scrollable
  • Menu uses Popper to position the component on the screen
  • The component handles positioning automatically; custom positioning is not currently supported
  • Menu can be closed by clicking any element outside of the Menu
  • aria-label is required and provides an accessible label for Menu
  • useMenu() provides necessary accessibility attributes for Menu that are required on the triggering element
Navigating a Menu with a keyboard
KeysAction
return, space, and arrow keys
return or escape keysClose
and arrow keys, or home and endFocus menu items
returnSelect item
a–zType the first letter of an action to move focus to that action.
I.e., typing "p" would move focus to the “Payroll” action.
React props
NameTypeDefaultDescription
aria-label  RequiredstringAccessible label for the element. Not displayed visually.
children  ReactNodeThe content of the Menu.
onClose  functionA callback for when the Menu is closed.
open  booleanfalseDetermines the visual treatment of the button.
reference  RequiredElementVirtualElementnullHTMLElement used to position the popover (typically provided by useMenu).

Menus are made up of several sub-components, each with their own special purpose and set of props (see: Menu anatomy).

Menu sub-components
SubcomponentPurpose
MenuItemUsed for items that only need to respond to an onClick event.
MenuItemLinkUsed for items that will navigate to a new URL, optionally with an onClick.
MenuGroupGroups elements using a required label; used in combination with MenuSeparator.
MenuSeparatorCreates a visual divider between groups of items.

The following testing snippet(s) offer suggestions for testing the component using React Testing Library with occasional help from Jest.

const MoreActionsMenu = () => {
const [menuProps, menuButtonProps] = useMenu();
return (
<>
<IconButton {...menuButtonProps} title="More actions">
<More />
</IconButton>
<Menu {...menuProps} aria-label="More actions for email">
<MenuItem before={<MailClosed />} onClick={onClick}>
Inbox
</MenuItem>
<MenuItem before={<StarOutline />} onClick={onClick}>
Starred
</MenuItem>
<MenuItem before={<Clock />} onClick={onClick}>
Snoozed
</MenuItem>
<MenuItem before={<Upload />} onClick={onClick}>
Sent
</MenuItem>
<MenuItem before={<Document />} onClick={onClick}>
Drafts
</MenuItem>
<MenuSeparator />
<MenuGroup label="Meetings">
<MenuItem before={<Plus />} onClick={onClick}>
New meeting
</MenuItem>
<MenuItem before={<CalendarOutline />} onClick={onClick}>
My meetings
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuItemLink
before={<ExternalLink />}
color="error"
href="https://gusto.com"
onClick={onClick}
>
Sign out
</MenuItemLink>
</Menu>
</>
);
};
render(<MoreActionsMenu />);
const menuButton = screen.getByLabelText('More actions');
// Alternatively using getByRole:
// const menuButton = screen.getByRole('button', {
// name: 'More actions',
// });
act(() => {
userEvent.click(menuButton);
});
const menu = screen.getByLabelText('More actions for email');
// Alternatively using getByRole:
// const menu = screen.getByRole('menu', {
// name: 'More actions for email',
// });
expect(menu).toBeVisible();
// Standard MenuItem
const inboxMenuItem = within(menu).getByRole('menuitem', {
name: 'Inbox',
});
act(() => {
userEvent.click(inboxMenuItem);
});
expect(menu).not.toBeVisible();
act(() => {
userEvent.click(menuButton);
});
expect(menu).toBeVisible();
// MenuItemLink
const signOutMenuItem = within(menu).getByRole('menuitem', {
name: 'Sign out',
});
act(() => {
userEvent.click(signOutMenuItem);
});
expect(menu).not.toBeVisible();
const separators = within(menu).queryAllByRole('separator');
expect(separators).toHaveLength(2);
const mettingsGroup = within(menu).getByRole('group', {
name: 'Meetings',
});
const newMeetingItem = within(meetingsGroup).getByRole('menuitem', {
name: 'New meeting',
});
act(() => {
userEvent.click(newMeetingItem);
});
expect(menu).not.toBeVisible();