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"andaria-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>