React
Hover Design System exports the following components for rendering menus:
import { Menu, MenuItem, MenuItemOption, MenuGroup, MenuOptionGroup, MenuDivider,} from '@hoverinc/design-system-react-web';
Menu
– The wrapper component provides context, state, and focus management, requires atrigger
prop.MenuItem
– The trigger that handles menu selection. Must be a direct child of aMenuList
.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 withMenuOptionGroup
.
Usage
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
.
Menu container styles
Use menuListProps
to pass props to the div
that contains MenuItem
s. This
container composes Box
so you can pass all Box
props to change its style.
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
.
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.
Example with images
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.
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.
Rendering the Menu in a Portal
To render menus in a Portal
, pass the inPortal
prop.
Groups
To group related MenuItem
s, use the MenuGroup
component and pass it a
title
for the group name.
Items as links
To render a MenuItem
as a link, use the attributes as
and href
.
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 iconBefore={iconFilter}>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.
Accessibility
Keyboard Interaction
Key | Action |
---|---|
Enter or Space | When MenuButton receives focus, opens the menu and places focus on the first menu item. |
ArrowDown | When MenuButton receives focus, opens the menu and moves focus to the first menu item. |
ArrowUp | When MenuButton receives focus, opens the menu and moves focus to the last menu item. |
Escape | When the menu is open, closes the menu and sets focus to the MenuButton . |
Tab | no effect |
Home | When the menu is open, moves focus to the first item. |
End | When the menu is open, moves focus to the last item. |
A-Z or a-z | When 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 tobutton
.aria-haspopup
is set tomenu
.- When the menu is displayed,
aria-expanded
is set totrue
. aria-controls
is set to theid
of theMenuList
.
For MenuList
:
role
is set tomenu
.aria-orientation
is set tovertical
.
For MenuItem
:
role
is set tomenuitem
.- Gets one of these roles
menuitem
/menuitemradio
/menuitemcheckbox
.
React Native
Menu is built with Tamagui and provides a dropdown interface with trigger, dropdown, items, and dividers for iOS and Android platforms.
Usage
import { iconChevronDown, iconTrash } from '@hoverinc/icons/native';import React, { useState } from 'react';import { Heading, Icon, Menu, XStack,} from '@hoverinc/design-system-react-native';const App = () => { const [open, setOpen] = useState(false); return ( <Menu onOpenChange={() => setOpen(!open)} open={open} placement="bottom-end" > <Menu.Trigger> <XStack alignItems="center" gap="$100" pressStyle={{ opacity: 0.5 }}> <Heading size="xs">Homeowner</Heading> <Icon icon={iconChevronDown} size="tiny" /> </XStack> </Menu.Trigger> <Menu.Dropdown> <Menu.Item isDisabled>Invited to scan</Menu.Item> <Menu.Divider /> <Menu.Item isSelectable isSelected> Selected Item </Menu.Item> <Menu.Item onPress={() => setOpen(false)}>Send reminder</Menu.Item> <Menu.Item onPress={() => setOpen(false)}>Copy link</Menu.Item> <Menu.Item color="$dangerSecondary" iconAfter={iconTrash} onPress={() => setOpen(false)} > Sign Out </Menu.Item> </Menu.Dropdown> </Menu> );};
Components
The Menu component exports the following sub-components:
Menu
– The main wrapper component that provides context and state managementMenu.Trigger
– The trigger element that opens the menu (supports scope prop)Menu.Dropdown
– The container for menu itemsMenu.Item
– Individual menu items with support for selection (isSelectable
,isSelected
), disabled state, icons, and custom colorsMenu.Divider
– Visual separator between menu items
Menu Item States
Menu items support several states and configurations:
// Basic menu item<Menu.Item>Basic Item</Menu.Item>// Selectable state (shows checkmark when selected)<Menu.Item isSelectable>Selectable Item</Menu.Item>// Selected state (requires isSelectable to be true)<Menu.Item isSelectable isSelected>Selected Item</Menu.Item>// Disabled state<Menu.Item isDisabled>Disabled Item</Menu.Item>// With icon after<Menu.Item iconAfter={iconChevronRight}>Item with Icon</Menu.Item>// With custom color<Menu.Item color="$dangerSecondary">Danger Item</Menu.Item>
Selection Behavior
Menu items support two related props for selection:
isSelectable
: Makes the item selectable (shows checkmark icon when selected)isSelected
: Marks the item as currently selected (requiresisSelectable
to betrue
)
// ✅ Valid - selectable but not selected<Menu.Item isSelectable>Option 1</Menu.Item>// ✅ Valid - selectable and selected<Menu.Item isSelectable isSelected>Selected Option</Menu.Item>// ❌ Invalid - selected but not selectable (TypeScript error)<Menu.Item isSelected>Selected Option</Menu.Item>
When isSelectable
is true
, the item will:
- Show a checkmark icon when
isSelected
istrue
- Have different padding to accommodate the checkmark
- Support selection state styling
Placement
The menu can be positioned relative to its trigger using the placement
prop:
<Menu placement="bottom-start">...</Menu><Menu placement="top-end">...</Menu><Menu placement="left">...</Menu><Menu placement="right">...</Menu>
Controlled vs Uncontrolled
The Menu component supports both controlled and uncontrolled usage:
// Controlledconst [open, setOpen] = useState(false);<Menu open={open} onOpenChange={setOpen}>...</Menu>// Uncontrolled<Menu defaultOpen={false}>...</Menu>
Scoped Menus
For complex applications with multiple menus, you can use scoped menus to isolate menu behavior:
<Menu scope="myScope"> <Menu.Trigger scope="myScope"> <XStack alignItems="center" gap="$100"> <Heading size="xs">Scoped Menu</Heading> <Icon icon={iconChevronDown} size="tiny" /> </XStack> </Menu.Trigger> <Menu.Dropdown> <Menu.Item>Scoped Item 1</Menu.Item> <Menu.Item>Scoped Item 2</Menu.Item> </Menu.Dropdown></Menu>