adev/src/content/guide/aria/menu.md
A menu offers a list of actions or options to users, typically appearing in response to a button click or right-click. Menus support keyboard navigation with arrow keys, submenus, checkboxes, radio buttons, and disabled items.
<docs-tab-group> <docs-tab label="Basic"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Material"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Retro"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css"/> </docs-code-multifile> </docs-tab> </docs-tab-group>Menus work well for presenting lists of actions or commands that users can choose from.
Use menus when:
Avoid menus when:
Create a dropdown menu by pairing a trigger button with a menu. The trigger opens and closes the menu.
<docs-tab-group> <docs-tab label="Basic"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Material"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Retro"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css"/> </docs-code-multifile> </docs-tab> </docs-tab-group>The menu automatically closes when a user selects an item or presses Escape.
Context menus appear at the cursor position when users right-click an element.
<docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-context/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-context/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-context/app/app.html"/> </docs-code-multifile>Position the menu using the contextmenu event coordinates.
A standalone menu doesn't require a trigger and remains visible in the interface.
<docs-tab-group> <docs-tab label="Basic"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-standalone/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-standalone/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Material"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.css"/> </docs-code-multifile> </docs-tab> <docs-tab label="Retro"> <docs-code-multifile preview hideCode path="adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.ts"> <docs-code header="app.ts" path="adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.ts"/> <docs-code header="app.html" path="adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.html"/> <docs-code header="app.css" path="adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.css"/> </docs-code-multifile> </docs-tab> </docs-tab-group>Standalone menus work well for always-visible action lists or navigation.
Disable specific menu items using the disabled input. Control focus behavior with softDisabled.
When [softDisabled]="true", disabled items can receive focus but cannot be activated. When [softDisabled]="false", disabled items are skipped during keyboard navigation.
Angular Aria provides component harnesses for testing menu components. Here is an example of how to use the harnesses in a component test:
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing';
import {MyMenuComponent} from './my-menu'; // Your component
describe('MyMenuComponent', () => {
let fixture: ComponentFixture<MyMenuComponent>;
let loader: HarnessLoader;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [MyMenuComponent],
});
fixture = TestBed.createComponent(MyMenuComponent);
await fixture.whenStable();
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should open menu and click item', async () => {
// Load the menu harness by its trigger text
const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
// Verify initial state
expect(await menu.isOpen()).toBe(false);
// Open the menu
await menu.open();
expect(await menu.isOpen()).toBe(true);
// Get items
const items = await menu.getItems();
expect(items.length).toBe(3);
expect(await items[0].getText()).toBe('Item 1');
// Click first item
await items[0].click();
// Menu should close after selection (depending on your implementation)
expect(await menu.isOpen()).toBe(false);
});
it('should interact with submenus', async () => {
const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
await menu.open();
// Get the item that triggers a submenu
const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'}));
expect(await subItem.hasSubmenu()).toBe(true);
// Open submenu
await subItem.click();
const submenu = await subItem.getSubmenu();
expect(submenu).toBeTruthy();
expect(await submenu!.isOpen()).toBe(true);
// Interact with submenu items
const subItems = await submenu!.getItems();
expect(subItems.length).toBe(1);
});
});
The container directive for menu items.
| Property | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables all items in the menu |
wrap | boolean | true | Whether keyboard navigation wraps at edges |
softDisabled | boolean | true | When true, disabled items are focusable but not interactive |
| Method | Parameters | Description |
|---|---|---|
close | none | Closes the menu |
A horizontal container for multiple menus.
| Property | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the entire menubar |
wrap | boolean | true | Whether keyboard navigation wraps at edges |
softDisabled | boolean | true | When true, disabled items are focusable but not interactive |
An individual item within a menu.
| Property | Type | Default | Description |
|---|---|---|---|
value | any | — | Required. Value for this item |
role | 'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'menuitem' | The ARIA role for the menu item |
disabled | boolean | false | Disables this menu item |
submenu | Menu | — | Reference to a submenu |
searchTerm | string | '' | Search term for typeahead (supports two-way binding) |
| Property | Type | Description |
|---|---|---|
active | Signal<boolean> | Whether the item currently has focus |
expanded | Signal<boolean> | Whether the submenu is expanded |
hasPopup | Signal<boolean> | Whether the item has an associated submenu |
NOTE: MenuItem does not expose public methods. Use the submenu input to associate submenus with menu items.
A button or element that opens a menu.
| Property | Type | Default | Description |
|---|---|---|---|
menu | Menu | — | Required. The menu to trigger |
disabled | boolean | false | Disables the trigger |
softDisabled | boolean | true | When true, disabled trigger is focusable |
| Property | Type | Description |
|---|---|---|
expanded | Signal<boolean> | Whether the menu is currently open |
hasPopup | Signal<boolean> | Whether the trigger has an associated menu |
| Method | Parameters | Description |
|---|---|---|
open | none | Opens the menu |
close | none | Closes the menu |
toggle | none | Toggles the menu open/closed |