Standard Toolkit
Toolkits@accelint/design-toolkitComponents

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 popoverProps prop 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

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

PropTypeDefaultRequired
childrenReactNode-Yes
variant'cozy' | 'compact''cozy'No
selectionMode'none' | 'single' | 'multiple''single'No
popoverPropsOmit<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 applications
  • compact - 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
interface MenuItemProps extends AriaMenuItemProps {
  classNames?: {
    item?: AriaMenuItemProps['className'];
    text?: AriaTextProps['className'];
    more?: IconProps['className'];
    icon?: IconProps['className'];
    hotkey?: string;
  };
  color?: 'info' | 'serious' | 'critical';
}

Props

PropTypeDefaultRequired
childrenReactNode-Yes
color'info' | 'serious' | 'critical''info'No
isDisabledbooleanfalseNo
classNames{ item?, icon?, hotkey?, more?, text? }-No

color

Semantic color variants:

  • info - Default neutral styling
  • serious - Warning or cautionary actions
  • critical - Destructive actions (delete, remove)

Good to know: String children are automatically wrapped in a MenuItemLabel. For complex layouts, use MenuItemLabel and MenuItemDescription explicitly.

Primary text label for menu items. Renders as a Text component with the label slot.

Props

PropTypeDefaultRequired
childrenReactNode-Yes
classNamestring-No

Secondary descriptive text for menu items. Renders as a Text component with the description slot.

Props

PropTypeDefaultRequired
childrenReactNode-Yes
classNamestring-No

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

PropTypeDefaultRequired
childrenReactNode-Yes
titlestring | ReactElement-No
classNames{ section?, header? }-No

title

Section header displayed above grouped items. Accepts strings or React elements for custom formatting.

Visual divider between menu items or sections.

Props

PropTypeDefaultRequired
classNamestring-No

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.

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

On this page