Accessible Popup Menus
Accessible Popup Menus
Accessible popup menus enable keyboard and screen reader users to navigate and select from action lists that appear on demand. Proper implementation requires correct ARIA roles, keyboard patterns, and focus management.
What Are Accessible Popup Menus
Popup menus present a list of actions or options that appear when triggered, typically by clicking a button. Examples include more actions menus, context menus, and dropdown menus that execute actions (as opposed to navigation menus or form selects).
The ARIA menu pattern uses role=“menu” for the container and role=“menuitem” for each action. A menu button with aria-haspopup=“menu” triggers the menu. This pattern differs from navigation menus (lists of links) and select dropdowns (form controls).
WCAG requirements include keyboard operability (2.1.1) and proper role/state communication (4.1.2). The menu pattern must support complete keyboard navigation without mouse interaction.
How Accessible Popup Menus Work
The menu button trigger uses aria-haspopup=“menu” to indicate it opens a menu. aria-expanded communicates whether the menu is open or closed. When activated with Enter, Space, or arrow keys, the menu appears.
The menu container uses role=“menu” and contains children with role=“menuitem”. Optionally, role=“menuitemcheckbox” or role=“menuitemradio” can indicate selectable options within the menu.
Keyboard navigation within the menu follows these patterns:
- Down arrow: Move to next item
- Up arrow: Move to previous item
- Home: Move to first item
- End: Move to last item
- Enter/Space: Activate the current item
- Escape: Close menu and return focus to trigger
Focus management differs from tab-based navigation. The menu uses roving tabindex: only one item has tabindex=“0” at a time, and arrow keys move this designation. Tab should close the menu rather than move between items.
When the menu opens, focus should move to the first menu item (or a previously selected item if relevant). When the menu closes, focus returns to the trigger button.
Key Considerations
- Use aria-haspopup=“menu” on the trigger button
- Manage aria-expanded to reflect open/closed state
- Implement arrow key navigation within the menu
- Close the menu with Escape key
- Return focus to the trigger when menu closes
- Use roving tabindex rather than Tab navigation between items
- Provide distinct focus indication on the current menu item
Common Questions
When should menu versus listbox patterns be used?
Menu (role=“menu”) is appropriate for actions: commands that execute when selected. “Edit,” “Delete,” and “Share” are menu items. The menu closes after selection, and the action executes.
Listbox (role=“listbox”) is appropriate for selection: choosing values from a list. Form selects and dropdown value pickers use listbox. The pattern for keyboard navigation differs slightly between these roles.
How should nested submenus work?
Submenus add complexity to popup menus. A menu item that triggers a submenu uses aria-haspopup=“menu” and aria-expanded. Right arrow opens the submenu (left arrow in RTL). Left arrow closes the submenu and returns to parent.
Focus moves into the submenu when it opens. Escape closes only the current level, returning to the parent menu. Deeply nested menus become difficult to navigate; limiting nesting depth improves usability.
Should menus support type-ahead search?
Type-ahead allows users to type characters to jump to matching menu items. This feature helps navigate long menus quickly. Typing “d” moves focus to the first item starting with “d.”
Type-ahead is optional but improves usability for longer menus. Implementation watches for character keys and moves focus to matching items. A brief delay between characters allows multi-character searches.
Summary
Accessible popup menus use the ARIA menu pattern with proper roles, arrow key navigation, and focus management that moves focus into the menu on open and returns it to the trigger on close. The pattern suits action lists that execute commands, distinct from navigation menus and selection dropdowns.
Buoy scans your codebase for design system inconsistencies before they ship
Detect Design Drift Free