ChatGPT UI Menu - Horizon AI Template

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

Import#

Chakra UI exports 8 components for rendering menus:

  • Menu: The wrapper component provides context, state, and focus management.
  • MenuList: The wrapper for the menu items. Must be a direct child of Menu.
  • MenuButton: The trigger for the menu list. Must be a direct child of Menu.
  • 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.
import {
Menu,
MenuButton,
MenuList,
MenuItem,
MenuItemOption,
MenuGroup,
MenuOptionGroup,
MenuIcon,
MenuCommand,
MenuDivider,
} from "@chakra-ui/react"

Usage#

<Menu>
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
Actions
</MenuButton>
<MenuList>
<MenuItem>Download</MenuItem>
<MenuItem>Create a Copy</MenuItem>
<MenuItem>Mark as Draft</MenuItem>
<MenuItem>Delete</MenuItem>
<MenuItem>Attend a Workshop</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
Actions
</MenuButton>
<MenuList>
<MenuItem>Download</MenuItem>
<MenuItem>Create a Copy</MenuItem>
<MenuItem>Mark as Draft</MenuItem>
<MenuItem>Delete</MenuItem>
<MenuItem>Attend a Workshop</MenuItem>
</MenuList>
</Menu>

Accessing the internal state#

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

<Menu>
{({ isOpen }) => (
<>
<MenuButton isActive={isOpen} as={Button} rightIcon={<ChevronDownIcon />}>
{isOpen ? "Close" : "Open"}
</MenuButton>
<MenuList>
<MenuItem>Download</MenuItem>
<MenuItem onClick={() => alert("Kagebunshin")}>Create a Copy</MenuItem>
</MenuList>
</>
)}
</Menu>
<Menu>
{({ isOpen }) => (
<>
<MenuButton isActive={isOpen} as={Button} rightIcon={<ChevronDownIcon />}>
{isOpen ? "Close" : "Open"}
</MenuButton>
<MenuList>
<MenuItem>Download</MenuItem>
<MenuItem onClick={() => alert("Kagebunshin")}>Create a Copy</MenuItem>
</MenuList>
</>
)}
</Menu>

Customizing the button#

The default MenuButton can be styled using the usual styled-system props, but it starts off plainly styled.

Using the as prop of the MenuButton, you can render a custom component instead of the default MenuButton. For instance, you can use Chakra's Button component, or your own custom component.

Custom components must take a ref prop which is assigned to the React component that triggers the menu opening. This is so that the MenuList popover can be positioned correctly. Without this, the MenuList will render in an undefined position.

Letter Navigation#

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

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

<Menu>
<MenuButton
px={4}
py={2}
transition="all 0.2s"
borderRadius="md"
borderWidth="1px"
_hover={{ bg: "gray.400" }}
_expanded={{ bg: "blue.400" }}
_focus={{ boxShadow: "outline" }}
>
File <ChevronDownIcon />
</MenuButton>
<MenuList>
<MenuItem>New File</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuDivider />
<MenuItem>Open...</MenuItem>
<MenuItem>Save File</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton
px={4}
py={2}
transition="all 0.2s"
borderRadius="md"
borderWidth="1px"
_hover={{ bg: "gray.400" }}
_expanded={{ bg: "blue.400" }}
_focus={{ boxShadow: "outline" }}
>
File <ChevronDownIcon />
</MenuButton>
<MenuList>
<MenuItem>New File</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuDivider />
<MenuItem>Open...</MenuItem>
<MenuItem>Save File</MenuItem>
</MenuList>
</Menu>

Just another example#

<Menu>
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
Your Cats
</MenuButton>
<MenuList>
<MenuItem minH="48px">
<Image
boxSize="2rem"
borderRadius="full"
src="https://placekitten.com/100/100"
alt="Fluffybuns the destroyer"
mr="12px"
/>
<span>Fluffybuns the Destroyer</span>
</MenuItem>
<MenuItem minH="40px">
<Image
boxSize="2rem"
borderRadius="full"
src="https://placekitten.com/120/120"
alt="Simon the pensive"
mr="12px"
/>
<span>Simon the pensive</span>
</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
Your Cats
</MenuButton>
<MenuList>
<MenuItem minH="48px">
<Image
boxSize="2rem"
borderRadius="full"
src="https://placekitten.com/100/100"
alt="Fluffybuns the destroyer"
mr="12px"
/>
<span>Fluffybuns the Destroyer</span>
</MenuItem>
<MenuItem minH="40px">
<Image
boxSize="2rem"
borderRadius="full"
src="https://placekitten.com/120/120"
alt="Simon the pensive"
mr="12px"
/>
<span>Simon the pensive</span>
</MenuItem>
</MenuList>
</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.

<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<HamburgerIcon />}
variant="outline"
/>
<MenuList>
<MenuItem icon={<AddIcon />} command="⌘T">
New Tab
</MenuItem>
<MenuItem icon={<ExternalLinkIcon />} command="⌘N">
New Window
</MenuItem>
<MenuItem icon={<RepeatIcon />} command="⌘⇧N">
Open Closed Tab
</MenuItem>
<MenuItem icon={<EditIcon />} command="⌘O">
Open File...
</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<HamburgerIcon />}
variant="outline"
/>
<MenuList>
<MenuItem icon={<AddIcon />} command="⌘T">
New Tab
</MenuItem>
<MenuItem icon={<ExternalLinkIcon />} command="⌘N">
New Window
</MenuItem>
<MenuItem icon={<RepeatIcon />} command="⌘⇧N">
Open Closed Tab
</MenuItem>
<MenuItem icon={<EditIcon />} command="⌘O">
Open File...
</MenuItem>
</MenuList>
</Menu>

Lazily mounting MenuItem#

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 MenuList 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.

<Menu isLazy>
<MenuButton>Open menu</MenuButton>
<MenuList>
{/* MenuItems are not rendered unless Menu is open */}
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</MenuList>
</Menu>
<Menu isLazy>
<MenuButton>Open menu</MenuButton>
<MenuList>
{/* MenuItems are not rendered unless Menu is open */}
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</MenuList>
</Menu>

Rendering menu in a portal#

To render menus in a portal, import the Portal component and wrap the MenuList within the Portal.

<Menu>
<MenuButton>Open menu</MenuButton>
<Portal>
<MenuList>
<MenuItem>Menu 1</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</MenuList>
</Portal>
</Menu>
<Menu>
<MenuButton>Open menu</MenuButton>
<Portal>
<MenuList>
<MenuItem>Menu 1</MenuItem>
<MenuItem>New Window</MenuItem>
<MenuItem>Open Closed Tab</MenuItem>
<MenuItem>Open File</MenuItem>
</MenuList>
</Portal>
</Menu>

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

<Menu>
<MenuButton as={Button} colorScheme="pink">
Profile
</MenuButton>
<MenuList>
<MenuGroup title="Profile">
<MenuItem>My Account</MenuItem>
<MenuItem>Payments </MenuItem>
</MenuGroup>
<MenuDivider />
<MenuGroup title="Help">
<MenuItem>Docs</MenuItem>
<MenuItem>FAQ</MenuItem>
</MenuGroup>
</MenuList>
</Menu>
<Menu>
<MenuButton as={Button} colorScheme="pink">
Profile
</MenuButton>
<MenuList>
<MenuGroup title="Profile">
<MenuItem>My Account</MenuItem>
<MenuItem>Payments </MenuItem>
</MenuGroup>
<MenuDivider />
<MenuGroup title="Help">
<MenuItem>Docs</MenuItem>
<MenuItem>FAQ</MenuItem>
</MenuGroup>
</MenuList>
</Menu>

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

<Menu closeOnSelect={false}>
<MenuButton as={Button} colorScheme="brand">
MenuItem
</MenuButton>
<MenuList minWidth="240px">
<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>
</MenuList>
</Menu>
<Menu closeOnSelect={false}>
<MenuButton as={Button} colorScheme="brand">
MenuItem
</MenuButton>
<MenuList minWidth="240px">
<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>
</MenuList>
</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.

Props#

arrowPadding

Description

The padding required to prevent the arrow from reaching the very edge of the popper.

Type
number
Default
8

autoSelect

Description

If true, the first enabled menu item will receive focus and be selected when the menu opens.

Type
boolean
Default
true

boundary

Description

The boundary area for the popper. Used within the preventOverflow modifier

Type
HTMLElement | "clippingParents" | "scrollParent"
Default
"clippingParents"

closeOnBlur

Description

If true, the menu will close when you click outside the menu list

Type
boolean
Default
true

closeOnSelect

Description

If true, the menu will close when a menu item is clicked

Type
boolean
Default
true

colorScheme

Description

Color Schemes for Menu are not implemented in the default theme. You can extend the themeto implement them.

Type
"brand" | "whiteAlpha" | "blackAlpha" | "gray" | "red" | "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" | "purple" | "pink" | "linkedin" | "facebook" | ... 4 more ...

computePositionOnMount

Description

If true, the menu will be positioned when it mounts (even if it's not open). Note 🚨: We don't recommend using this in a menu/popover intensive UI or page as it might affect scrolling performance.

Type
boolean

defaultIsOpen

Type
boolean

enabled

Description

Whether the popper.js should be enabled

Type
boolean

eventListeners

Description

If provided, determines whether the popper will reposition itself on scroll and resize of the window.

Type
boolean | { scroll?: boolean; resize?: boolean | undefined; } | undefined

flip

Description

If true, the popper will change its placement and flip when it's about to overflow its boundary area.

Type
boolean
Default
true

gutter

Description

The distance or margin between the reference and popper. It is used internally to create an offset modifier. NB: If you define offset prop, it'll override the gutter.

Type
number
Default
8

id

Type
string

isLazy

Description

Performance 🚀: If true, the MenuItem rendering will be deferred until the menu is open.

Type
boolean

isOpen

Type
boolean

lazyBehavior

Description

Performance 🚀: The lazy behavior of menu's content when not visible. Only works when `isLazy={true}` - "unmount": The menu's content is always unmounted when not open. - "keepMounted": The menu's content initially unmounted, but stays mounted when menu is open.

Type
"unmount" | "keepMounted"
Default
"unmount"

matchWidth

Description

If true, the popper will match the width of the reference at all times. It's useful for autocomplete, `date-picker` and select patterns.

Type
boolean

modifiers

Description

Array of popper.js modifiers. Check the docs to see the list of possible modifiers you can pass. @see Docs https://popper.js.org/docs/v2/modifiers/

Type
Partial<Modifier<string, any>>[]

offset

Description

The main and cross-axis offset to displace popper element from its reference element.

Type
[crossAxis: number, mainAxis: number]

onClose

Type
(() => void)

onOpen

Type
(() => void)

placement

Description

The placement of the popper relative to its reference.

Type
"bottom" | "left" | "right" | "top" | "auto" | "auto-start" | "auto-end" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end"
Default
"bottom"

preventOverflow

Description

If true, will prevent the popper from being cut off and ensure it's visible within the boundary area.

Type
boolean
Default
true

size

Description

Sizes for Menu are not implemented in the default theme. You can extend the themeto implement them.

Type
string

strategy

Description

The CSS positioning strategy to use.

Type
"fixed" | "absolute"
Default
"absolute"

variant

Description

Variants for Menu are not implemented in the default theme. You can extend the themeto implement them.

Type
string

MenuButton composes Box so you can pass all Box props to change its style.

MenuList composes Box so you can pass all Box props to change its style.

closeOnSelect

Description

Overrides the parent menu's closeOnSelect prop.

Type
boolean

command

Description

Right-aligned label text content, useful for displaying hotkeys.

Type
string

commandSpacing

Description

The spacing between the command and menu item's label.

Type
SystemProps["ml"]

icon

Description

The icon to render before the menu item's label.

Type
React.ReactElement

iconSpacing

Description

The spacing between the icon and menu item's label.

Type
SystemProps["mr"]

isDisabled

Description

If true, the menuitem will be disabled

Type
boolean

isFocusable

Description

If true and the menuitem is disabled, it'll remain keyboard-focusable

Type
boolean

MenuGroup composes Box so you can pass all Box props to change its style.

defaultValue

Type
string | string[]

onChange

Type
((value: string | string[]) => void)

type

Type
"checkbox" | "radio"

value

Type
string | string[]

MenuItemOption composes Box so you can pass all box props in addition to these:

closeOnSelect

Description

Overrides the parent menu's closeOnSelect prop.

Type
boolean

command

Description

Right-aligned label text content, useful for displaying hotkeys.

Type
string

commandSpacing

Description

The spacing between the command and menu item's label.

Type
SystemProps["ml"]

icon

Description

The icon to render before the menu item's label.

Type
React.ReactElement

iconSpacing

Description

The spacing between the icon and menu item's label.

Type
SystemProps["mr"]

isChecked

Type
boolean

isDisabled

Description

If true, the menuitem will be disabled

Type
boolean

isFocusable

Description

If true and the menuitem is disabled, it'll remain keyboard-focusable

Type
boolean

type

Type
"checkbox" | "radio"

value

Type
string

Horizon AI Template © 2021-2023 Copyright. All Rights Reserved.

  • Blog
  • License