Menu
Accessible dropdown menu supporting keyboard navigation, nested submenus, and flexible item rendering
Accessible dropdown menu supporting keyboard navigation, nested submenus, single/multiple selection modes, and flexible item rendering with links and sections.
Installation
npm install --save @godaddy/antaresProps
size?"sm" | "md" | undefined'md'Size variant for menu items
selectionMode?RACSelectionMode | undefined—The type of selection that is allowed in the collection
selectedKeys?"all" | Iterable<RACKey> | undefined—The currently selected keys in the collection (controlled)
defaultSelectedKeys?"all" | Iterable<RACKey> | undefined—The initial selected keys in the collection (uncontrolled)
disabledKeys?Iterable<RACKey> | undefined—The item keys that are disabled
onAction?((key: RACKey) => void) | undefined—Handler that is called when an item is selected
onSelectionChange?((keys: RACSelection) => void) | undefined—Handler that is called when the selection changes
autoFocus?boolean | "first" | "last" | undefined—Whether the element should receive focus on render
items?Iterable<T> | undefined—Item objects in the collection
children?React.ReactNode | ((item: T) => React.ReactNode)—The contents of the collection.
escapeKeyBehavior?"none" | "clearSelection" | undefined'clearSelection'Whether pressing the escape key should clear selection in the menu or not. Most experiences should not modify this option as it eliminates a keyboard user's ability to easily clear selection. Only use if the escape key is being handled externally or should not trigger selection clearing contextually.
shouldFocusWrap?boolean | undefined—Whether keyboard navigation is circular.
onClose?(() => void) | undefined—Handler that is called when the menu should close after selecting an item.
disallowEmptySelection?boolean | undefined—Whether the collection allows empty selection.
id?string | undefined—The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
className?ClassNameOrFunction<MenuRenderProps> | undefined'react-aria-Menu'The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state.
renderEmptyState?(() => React.ReactNode) | undefined—Provides content to display when there are no items in the list.
shouldCloseOnSelect?boolean | undefined—Whether the menu should close when the menu item is selected.
dependencies?readonly any[] | undefined—Values that should invalidate the item cache when using dynamic collections.
style?StyleOrFunction<MenuRenderProps> | undefined—The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state.
render?DOMRenderFunction<"div", MenuRenderProps> | undefined—Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components. Requirements: - You must render the expected element type (e.g. if `<button>` is expected, you cannot render an `<a>`). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.
slot?string | null | undefined—A slot name for the component. Slots allow the component to receive props from a parent component. An explicit `null` value indicates that the local props completely override all props received from a parent.
Examples
Basic
import { Button, Menu, MenuItem, MenuTrigger } from '@godaddy/antares';export function BasicExample() { return ( <MenuTrigger> <Button>Actions</Button> <Menu> <MenuItem id="new">New File</MenuItem> <MenuItem id="open">Open</MenuItem> <MenuItem id="save">Save</MenuItem> <MenuItem id="save-as">Save As...</MenuItem> <MenuItem id="close">Close</MenuItem> </Menu> </MenuTrigger> );}With Submenus
import { Button, Menu, MenuItem, MenuSeparator, MenuTrigger, SubmenuTrigger } from '@godaddy/antares';export function SubmenuExample() { return ( <MenuTrigger> <Button>Edit</Button> <Menu> <MenuItem id="undo">Undo</MenuItem> <MenuItem id="redo">Redo</MenuItem> <MenuSeparator /> <MenuItem id="cut">Cut</MenuItem> <MenuItem id="copy">Copy</MenuItem> <MenuItem id="paste">Paste</MenuItem> <MenuSeparator /> <SubmenuTrigger> <MenuItem id="share">Share</MenuItem> <Menu> <MenuItem id="email">Email</MenuItem> <MenuItem id="sms">SMS</MenuItem> <MenuItem id="slack">Slack</MenuItem> <SubmenuTrigger> <MenuItem id="social">Social Media</MenuItem> <Menu> <MenuItem id="twitter">Twitter</MenuItem> <MenuItem id="facebook">Facebook</MenuItem> <MenuItem id="linkedin">LinkedIn</MenuItem> </Menu> </SubmenuTrigger> </Menu> </SubmenuTrigger> </Menu> </MenuTrigger> );}With Selection
import { Button, Menu, MenuItem, MenuSeparator, MenuTrigger } from '@godaddy/antares';import type { Selection } from 'react-aria-components';import { useState } from 'react';export function SelectionExample() { const [selectedSingle, setSelectedSingle] = useState<Selection>(new Set(['left'])); const [selectedMultiple, setSelectedMultiple] = useState<Selection>(new Set(['bold', 'italic'])); return ( <div style={{ display: 'flex', gap: '1rem' }}> <MenuTrigger> <Button>Align (Single)</Button> <Menu selectionMode="single" selectedKeys={selectedSingle} onSelectionChange={setSelectedSingle}> <MenuItem id="left">Align Left</MenuItem> <MenuItem id="center">Align Center</MenuItem> <MenuItem id="right">Align Right</MenuItem> <MenuItem id="justify">Justify</MenuItem> </Menu> </MenuTrigger> <MenuTrigger> <Button>Format (Multiple)</Button> <Menu selectionMode="multiple" selectedKeys={selectedMultiple} onSelectionChange={setSelectedMultiple}> <MenuItem id="bold">Bold</MenuItem> <MenuItem id="italic">Italic</MenuItem> <MenuItem id="underline">Underline</MenuItem> <MenuItem id="strikethrough">Strikethrough</MenuItem> <MenuSeparator /> <MenuItem id="code">Code</MenuItem> </Menu> </MenuTrigger> </div> );}With Sections
import { Button, Menu, MenuHeader, MenuItem, MenuSection, MenuSeparator, MenuTrigger } from '@godaddy/antares';export function SectionsExample() { return ( <MenuTrigger> <Button>View</Button> <Menu> <MenuSection> <MenuHeader>Zoom</MenuHeader> <MenuItem id="zoom-in">Zoom In</MenuItem> <MenuItem id="zoom-out">Zoom Out</MenuItem> <MenuItem id="zoom-reset">Reset Zoom</MenuItem> </MenuSection> <MenuSeparator /> <MenuSection> <MenuHeader>Layout</MenuHeader> <MenuItem id="sidebar">Toggle Sidebar</MenuItem> <MenuItem id="panel">Toggle Panel</MenuItem> <MenuItem id="fullscreen">Fullscreen</MenuItem> </MenuSection> <MenuSeparator variant="partial" /> <MenuSection> <MenuHeader>Appearance</MenuHeader> <MenuItem id="theme-light">Light Theme</MenuItem> <MenuItem id="theme-dark">Dark Theme</MenuItem> <MenuItem id="theme-auto">Auto</MenuItem> </MenuSection> </Menu> </MenuTrigger> );}Customization
Data Attributes
React Aria Components automatically adds data attributes for styling different states:
Menu Items: data-hovered, data-focused, data-pressed, data-selected, data-disabled
Popover: data-entering, data-exiting
[data-selected] {
background-color: #eff6ff;
color: #1e40af;
}
[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
[data-entering] {
animation: slideIn 0.2s ease-out;
}Component Customization
Individual child components can be styled by passing className props:
<Menu>
<MenuItem id="special" className="premium-item">
Premium Feature
</MenuItem>
<MenuSection className="featured-section">
<MenuHeader>Featured</MenuHeader>
<MenuItem id="new">What's New</MenuItem>
</MenuSection>
</Menu>Accessibility
Keyboard Navigation
- Space/Enter: Opens menu and activates items
- Arrow Down/Up: Navigate through items
- Arrow Right: Opens submenu
- Arrow Left: Closes submenu
- Home/End: Jump to first/last item
- Escape: Closes menu
- Tab: Closes menu and moves to next element
- Type to select: Type to jump to matching items
Troubleshooting
Submenu Not Opening
// ❌ Wrong: MenuItem used directly
<MenuItem id="share">
Share
<Menu>
<MenuItem id="email">Email</MenuItem>
</Menu>
</MenuItem>
// ✅ Correct: SubmenuTrigger wrapping MenuItem and Menu
<SubmenuTrigger>
<MenuItem id="share">Share</MenuItem>
<Menu>
<MenuItem id="email">Email</MenuItem>
</Menu>
</SubmenuTrigger>Selection Not Working
// ❌ Wrong: Using both controlled and uncontrolled props
<Menu
selectedKeys={selected}
defaultSelectedKeys={['option1']}
selectionMode="single"
>
<MenuItem id="option1">Option 1</MenuItem>
</Menu>
// ✅ Controlled mode
<Menu
selectionMode="single"
selectedKeys={selected}
onSelectionChange={setSelected}
>
<MenuItem id="option1">Option 1</MenuItem>
</Menu>
// ✅ Uncontrolled mode
<Menu
selectionMode="single"
defaultSelectedKeys={['option1']}
>
<MenuItem id="option1">Option 1</MenuItem>
</Menu>Styling Not Applying
/* ❌ May not have enough specificity */
.item {
background-color: red;
}
/* ✅ Use data attributes for higher specificity */
.my-menu [data-selected] {
background-color: red;
}