Antares
Components

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/antares

Props

NameTypeDefault
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

// ❌ 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;
}

On this page