Blueprint

Menu

An accessible dropdown menu for the common dropdown menu button design pattern. Menu uses roving tabIndex for focus management.

React

Blueprint exports the following components for rendering menus:


import {
Menu,
MenuItem,
MenuItemOption,
MenuGroup,
MenuOptionGroup,
MenuDivider,
} from '@hover/blueprint';

  • Menu – The wrapper component provides context, state, and focus management, requires a trigger prop.
  • MenuItem – The trigger that handles menu selection. Must be a direct child of a MenuList.
  • MenuGroup – A wrapper to group related menu items.
  • MenuDivider – A visual separator for menu items and groups.
  • MenuOptionGroup – A wrapper for checkable menu items (radio and checkbox).
  • MenuItemOption – The checkable menu item, to be used with MenuOptionGroup.

Usage

live

<Menu
trigger={
<Button iconAfter={iChevronDown} shape="box">
Actions
</Button>
}
inPortal
>
<MenuItem>Download</MenuItem>
<MenuItem>Create a Copy</MenuItem>
<MenuItem>Mark as Draft</MenuItem>
<MenuItem>Delete</MenuItem>
<MenuItem>Attend a Workshop</MenuItem>
</Menu>

Trigger

The trigger prop accepts any system component. If you're passing a custom component that composes a system component, it must accept a ref.

Trigger must accept a ref

A custom component needs to accept a ref so that the menu list can be positioned correctly. You can use forwardRef from @hover/blueprint to supply the ref along with being able to use system props. Without a ref, the menu list will render in an undefined position.

Accessing the internal state

To access the internal state of the Menu, use a function as children or trigger (commonly known as a render prop). You'll get access to the internal state isOpen and method onClose.

live

<Menu
trigger={({ isOpen }) => (
<Button iconAfter={isOpen ? iChevronUp : iChevronDown} shape="box">
{isOpen ? 'Close' : 'Open'}
</Button>
)}
inPortal
>
<MenuItem>Download</MenuItem>
<MenuItem onClick={() => alert('Kagebunshin')}>Create a Copy</MenuItem>
</Menu>

Letter navigation

When focus is on the trigger or within the list and you type a letter key, a search begins. Focus will move to the first MenuItem that starts with the letter you typed.

Try it out

Open the menu, try and type any letter, (say, S) to see the focus movement.

live

<Menu
trigger={
<Button iconAfter={iChevronDown} shape="box">
File
</Button>
}
inPortal
>
<MenuItem>New File</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuDivider />
<MenuItem>Open...</MenuItem>
<MenuItem>Save File</MenuItem>
</Menu>

Example with images

live

<Menu
trigger={
<Button iconAfter={iChevronDown} shape="box">
Your Cats
</Button>
}
inPortal
>
<MenuItem minH="48px">
<Image
boxSize="300"
borderRadius="900"
src="/images/kitten--100-100.jpg"
alt="Fluffybuns the destroyer"
mr="300"
/>
<span>Fluffybuns the Destroyer</span>
</MenuItem>
<MenuItem minH="40px">
<Image
boxSize="300"
borderRadius="full"
src="/images/kitten--120-120.jpg"
alt="Simon the pensive"
mr="300"
/>
<span>Simon the pensive</span>
</MenuItem>
</Menu>

Adding icons and commands

You can add icon to each MenuItem by passing the icon prop. To add a commands (or hotkeys) to menu items, you can use the command prop.

live

<Menu
trigger={
<IconButton icon={iMenu} shape="square" fill="outline" label="Options" />
}
inPortal
>
<MenuItem icon={<Icon icon={iFolderPlus} />} command="⌘T">
New Tab
</MenuItem>
<MenuItem icon={<Icon icon={iExternalLink} />} command="⌘N">
New Window
</MenuItem>
<MenuItem icon={<Icon icon={hRedo} />} command="⌘⇧N">
Open Closed Tab
</MenuItem>
<MenuItem icon={<Icon icon={iFileText} />} command="⌘O">
Open File...
</MenuItem>
</Menu>

Lazily mounting items

By default, the Menu component renders all children of MenuList to the DOM, meaning that invisible menu items are still rendered but are hidden by styles.

If you want to defer rendering of each children of the menu list until that menu is open, you can use the isLazy prop. This is useful if your Menu needs to be extra performant, or make network calls on mount that should only happen when the component is displayed.

live

<Menu
isLazy
trigger={
<Button iconAfter={iChevronDown} shape="box">
Open Menu
</Button>
}
inPortal
>
{/* MenuItems are not rendered unless Menu is open */}
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</Menu>

Rendering the Menu in a Portal

To render menus in a Portal, pass the inPortal prop.

Info

These live code blocks hide overflow, so we're setting inPortal on all the examples

live

<Menu
inPortal
isLazy
trigger={
<Button iconAfter={iChevronDown} shape="box">
Open Menu
</Button>
}
>
<MenuItem>Menu 1</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</Menu>

Groups

To group related MenuItems, use the MenuGroup component and pass it a title for the group name.

live

<Menu
inPortal
trigger={
<Button
fill="minimal"
iconBefore={iUser}
iconAfter={iChevronDown}
shape="box"
>
Profile
</Button>
}
>
<MenuGroup title="Profile">
<MenuItem>My Account</MenuItem>
<MenuItem>Payments </MenuItem>
</MenuGroup>
<MenuDivider />
<MenuGroup title="Help">
<MenuItem>Docs</MenuItem>
<MenuItem>FAQ</MenuItem>
</MenuGroup>
</Menu>

To render a MenuItem as a link, use the attributes as and href.

live

<Menu
inPortal
trigger={
<Button iconAfter={iChevronDown} shape="box">
Open Menu
</Button>
}
>
<MenuItem as="a" href="#" icon={<Icon icon={iExternalLink} />}>
Link 1
</MenuItem>
<MenuItem as="a" href="#" icon={<Icon icon={iExternalLink} />}>
Link 2
</MenuItem>
</Menu>

Option groups

You can compose a menu for table headers to help with sorting and filtering options. Use the MenuOptionGroup and MenuItemOption components.


<Menu
inPortal
trigger={
<Button fill="outline" iconBefore={iFilter} shape="box">
Filter
</Button>
}
>
<MenuOptionGroup defaultValue="asc" title="Order" type="radio">
<MenuItemOption value="asc">Ascending</MenuItemOption>
<MenuItemOption value="desc">Descending</MenuItemOption>
</MenuOptionGroup>
<MenuDivider />
<MenuOptionGroup title="Country" type="checkbox">
<MenuItemOption value="email">Email</MenuItemOption>
<MenuItemOption value="phone">Phone</MenuItemOption>
<MenuItemOption value="country">Country</MenuItemOption>
</MenuOptionGroup>
</Menu>

With arrow

Set hasArrow to display an arrow pointing from the menu to the trigger.

live

<Menu
hasArrow
trigger={
<IconButton
label="Actions"
icon={iMoreHorizontal}
shape="square"
fill="minimal"
/>
}
inPortal
>
<MenuItem>Download</MenuItem>
<MenuItem>Create a Copy</MenuItem>
<MenuItem>Mark as Draft</MenuItem>
<MenuItem>Delete</MenuItem>
<MenuItem>Attend a Workshop</MenuItem>
</Menu>

Accessibility

Keyboard Interaction

KeyAction
Enter or SpaceWhen MenuButton receives focus, opens the menu and places focus on the first menu item.
ArrowDownWhen MenuButton receives focus, opens the menu and moves focus to the first menu item.
ArrowUpWhen MenuButton receives focus, opens the menu and moves focus to the last menu item.
EscapeWhen the menu is open, closes the menu and sets focus to the MenuButton.
Tabno effect
HomeWhen the menu is open, moves focus to the first item.
EndWhen the menu is open, moves focus to the last item.
A-Z or a-zWhen the menu is open, moves focus to the next menu item with a label that starts with the typed character if such an menu item exists.

ARIA roles

For MenuButton:

  • role is set to button.
  • aria-haspopup is set to menu.
  • When the menu is displayed, aria-expanded is set to true.
  • aria-controls is set to the id of the MenuList.

For MenuList:

  • role is set to menu.
  • aria-orientation is set to vertical.

For MenuItem:

  • role is set to menuitem.
  • Gets one of these roles menuitem/menuitemradio/ menuitemcheckbox.