Standards · ARIA

Role Composite widget

menu

Marks a container as an application menu — the popup of File/Edit-style menus or context menus. There is no HTML equivalent. A site's navigation list is NOT a menu; use <nav> with <ul> of links instead.

When to use

For an application-style menu — the popup that appears from a “File” button or a right-click context menu, with arrow-key navigation and Escape-to-close. Children must be role="menuitem", role="menuitemcheckbox", role="menuitemradio", or role="separator".

The menu role is application semantics. The biggest single mistake on the web is using role="menu" for site navigation, which traps keyboard users in arrow-key navigation, mis-announces links as menu items, and breaks the user’s mental model.

Rule of thumb: if the trigger is a hyperlink and the items are hyperlinks, it is navigation, not a menu. Use <nav> and <ul>.

A menu MUST have an accessible name — use aria-label or aria-labelledby pointing at the trigger.

Keyboard + focus contract

Per the APG menu pattern:

  • The trigger button has aria-haspopup="menu" and aria-expanded. Activating the trigger opens the menu and moves focus to the first menuitem.
  • Inside the menu: Up/Down arrows move focus between items; Home/End jump to first/last.
  • Right arrow opens a submenu (if focused item has one); Left arrow returns to the parent menu.
  • Enter or Space activates the focused item and closes the menu.
  • Escape closes the menu and returns focus to the trigger.
  • Typeahead: typing a letter jumps to the next item starting with that letter.

Single tab-stop: only one menuitem at a time has tabindex="0"; the rest are -1.

Common failures

  • role="menu" on a <nav> element. Application semantics override navigation semantics — screen readers stop announcing it as a navigation landmark.
  • Menu without a paired aria-haspopup="menu" on its trigger.
  • Escape closes the menu but does not return focus to the trigger, leaving the user stranded.
  • Focus does NOT move to the first menuitem when the menu opens — users have to Tab through nothing.
  • Submenus that open on hover only, not on keyboard.

Example

<button id="actions" aria-haspopup="menu" aria-expanded="false" aria-controls="actionsMenu">
  Actions
</button>
<ul id="actionsMenu" role="menu" aria-labelledby="actions" hidden>
  <li role="menuitem" tabindex="0">Edit</li>
  <li role="menuitem" tabindex="-1">Duplicate</li>
  <li role="separator"></li>
  <li role="menuitem" tabindex="-1">Delete</li>
</ul>