Menu
Dropdown menu component with keyboard navigation, selection modes, and accessible ARIA patterns
Menu is composed of multiple subcomponents: Menu, MenuTrigger, MenuItem, MenuItemLabel, MenuItemDescription, MenuSeparator, MenuSection, and MenuSubmenu. Compose with Hotkey and Icon components for enhanced functionality.
Good to know: Menu includes its own internal Popover. Do NOT wrap Menu in a Popover component—use the
popoverPropsprop to configure placement and positioning.
Usage
import {
Menu,
MenuTrigger,
MenuItem,
MenuItemLabel,
Button,
} from '@accelint/design-toolkit';
<MenuTrigger>
<Button>Open Menu</Button>
<Menu>
<MenuItem>
<MenuItemLabel>Edit</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Copy</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Delete</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Reference
Menu
interface MenuProps<T> extends AriaMenuProps<T> {
classNames?: {
menu?: AriaMenuProps<T>['className'];
popover?: PopoverProps['className'];
};
popoverProps?: Omit<AriaPopoverProps, 'children' | 'className'>;
variant?: 'compact' | 'cozy';
ref?: RefAttributes<HTMLDivElement>;
}Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | - | Yes |
variant | 'cozy' | 'compact' | 'cozy' | No |
selectionMode | 'none' | 'single' | 'multiple' | 'single' | No |
popoverProps | Omit<PopoverProps, 'children' | 'className'> | - | No |
classNames | { menu?, popover? } | - | No |
onAction | (key: Key) => void | - | No |
onClose | () => void | - | No |
variant
Controls visual density:
cozy- Comfortable spacing for desktop applicationscompact- Reduced padding for space-constrained interfaces
popoverProps
Configure placement, offset, and positioning of the menu popover. Accepts all React Aria Popover props except children and className.
Common properties:
placement- Position relative to trigger (e.g.,'bottom start','top end')offset- Distance in pixels from trigger
MenuItem
interface MenuItemProps extends AriaMenuItemProps {
classNames?: {
item?: AriaMenuItemProps['className'];
text?: AriaTextProps['className'];
more?: IconProps['className'];
icon?: IconProps['className'];
hotkey?: string;
};
color?: 'info' | 'serious' | 'critical';
}Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | - | Yes |
color | 'info' | 'serious' | 'critical' | 'info' | No |
isDisabled | boolean | false | No |
classNames | { item?, icon?, hotkey?, more?, text? } | - | No |
color
Semantic color variants:
info- Default neutral stylingserious- Warning or cautionary actionscritical- Destructive actions (delete, remove)
Good to know: String children are automatically wrapped in a
MenuItemLabel. For complex layouts, useMenuItemLabelandMenuItemDescriptionexplicitly.
MenuItemLabel
Primary text label for menu items. Renders as a Text component with the label slot.
Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | - | Yes |
className | string | - | No |
MenuItemDescription
Secondary descriptive text for menu items. Renders as a Text component with the description slot.
Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | - | Yes |
className | string | - | No |
MenuSection
Groups related menu items with an optional header.
interface MenuSectionProps<T> extends AriaMenuSectionProps<T> {
classNames?: {
section?: AriaMenuSectionProps<T>['className'];
header?: string;
};
title?: string | ReactElement;
}Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | - | Yes |
title | string | ReactElement | - | No |
classNames | { section?, header? } | - | No |
title
Section header displayed above grouped items. Accepts strings or React elements for custom formatting.
MenuSeparator
Visual divider between menu items or sections.
Props
| Prop | Type | Default | Required |
|---|---|---|---|
className | string | - | No |
MenuTrigger
Manages open/close state and anchors menu position. Wraps a trigger element (typically a Button) and the Menu component.
Re-exports React Aria's MenuTrigger. See React Aria MenuTrigger for full API reference.
MenuSubmenu
Trigger for nested submenu flyouts. Wraps a MenuItem and nested Menu to create hierarchical navigation.
Re-exports React Aria's SubmenuTrigger. See React Aria Submenu for full API reference.
Examples
Example: Basic menu
<MenuTrigger>
<Button>Open Menu</Button>
<Menu>
<MenuItem>
<MenuItemLabel>Item 1</MenuItemLabel>
</MenuItem>
<MenuSeparator />
<MenuItem>
<MenuItemLabel>Item 2</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Example: Custom placement
<MenuTrigger>
<Button>Open Menu</Button>
<Menu popoverProps={{ placement: 'top end' }}>
<MenuItem>
<MenuItemLabel>Item 1</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Item 2</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Available placements: 'bottom', 'bottom start', 'bottom end', 'top', 'top start', 'top end', 'left', 'right', etc.
Example: Offset control
<MenuTrigger>
<Button>Open Menu</Button>
<Menu popoverProps={{ placement: 'bottom', offset: 8 }}>
<MenuItem>
<MenuItemLabel>Item</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Example: Action handler
<MenuTrigger>
<Button>Actions</Button>
<Menu onAction={(key) => console.log('Selected:', key)}>
<MenuItem id="edit">
<MenuItemLabel>Edit</MenuItemLabel>
</MenuItem>
<MenuItem id="delete">
<MenuItemLabel>Delete</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Each MenuItem requires an id prop when using onAction.
Example: Selection modes
// Single selection
<MenuTrigger>
<Button>Select One</Button>
<Menu
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<MenuItem id="item-1">
<MenuItemLabel>Item 1</MenuItemLabel>
</MenuItem>
<MenuItem id="item-2">
<MenuItemLabel>Item 2</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>
// Multiple selection
<MenuTrigger>
<Button>Select Multiple</Button>
<Menu
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<MenuItem id="item-1">
<MenuItemLabel>Item 1</MenuItemLabel>
</MenuItem>
<MenuItem id="item-2">
<MenuItemLabel>Item 2</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Example: Sections and separators
<MenuTrigger>
<Button>Open</Button>
<Menu>
<MenuSection title="File Actions">
<MenuItem>
<MenuItemLabel>New File</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Open File</MenuItemLabel>
</MenuItem>
</MenuSection>
<MenuSeparator />
<MenuItem>
<MenuItemLabel>Settings</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Example: Color variants
<Menu>
<MenuItem color="info">
<MenuItemLabel>Info Action</MenuItemLabel>
</MenuItem>
<MenuItem color="serious">
<MenuItemLabel>Warning Action</MenuItemLabel>
</MenuItem>
<MenuItem color="critical">
<MenuItemLabel>Delete</MenuItemLabel>
</MenuItem>
</Menu>Example: Disabled items
<Menu>
<MenuItem>
<MenuItemLabel>Available Action</MenuItemLabel>
</MenuItem>
<MenuItem isDisabled>
<MenuItemLabel>Disabled Action</MenuItemLabel>
</MenuItem>
</Menu>Example: With icons and descriptions
import { Icon } from '@accelint/design-toolkit';
import { Placeholder } from '@accelint/icons';
<Menu>
<MenuItem>
<Icon>
<Placeholder />
</Icon>
<MenuItemLabel>Blue Jay</MenuItemLabel>
<MenuItemDescription>Cyanocitta cristata</MenuItemDescription>
</MenuItem>
</Menu>Example: Submenu
<MenuTrigger>
<Button>Actions</Button>
<Menu>
<MenuItem>
<MenuItemLabel>New File</MenuItemLabel>
</MenuItem>
<MenuSubmenu>
<MenuItem>
<MenuItemLabel>Export</MenuItemLabel>
</MenuItem>
<Menu>
<MenuItem>
<MenuItemLabel>Export as PDF</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Export as CSV</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Export as JSON</MenuItemLabel>
</MenuItem>
</Menu>
</MenuSubmenu>
<MenuItem>
<MenuItemLabel>Delete</MenuItemLabel>
</MenuItem>
</Menu>
</MenuTrigger>Example: Dynamic section header
const CustomHeader = () => (
<div>
<div>Section Title</div>
<div>Subheader text</div>
</div>
);
<MenuTrigger>
<Button>Open Menu</Button>
<Menu>
<MenuItem>
<MenuItemLabel>Item</MenuItemLabel>
</MenuItem>
<MenuSection title={<CustomHeader />}>
<MenuItem>
<MenuItemLabel>Section Item</MenuItemLabel>
</MenuItem>
</MenuSection>
</Menu>
</MenuTrigger>Accessibility
- Full keyboard navigation with Arrow keys
- Type-ahead selection support
- Submenu navigation with Arrow Left/Right
- Escape closes menu and returns focus to trigger
- Screen reader announcements for selection changes
- ARIA roles and attributes for menu structure