Menu
An accessible dropdown or contextual menu built on @angular/aria — roving focus, typeahead, unlimited nested submenus, and checkable items with no interactive control inside a menuitem.
A dropdown of actions, a "kebab" row menu, a nested settings menu — Menu composes MenuItems (with optional
nested SubMenus) inside MenuContent, wired to a trigger with MenuTriggerDirective. It is itself a
popover — opening, positioning, dismissal and focus restoration all come from the same native Popover API
Popover is built on, applied as a host directive, so there is no <div popover> wrapper to
write yourself.
Concept — roving focus
A menu's items are not each individually tabbable: Tab leaves the whole menu in one step, and ↓/↑/Home/
End move a single "active" focus between items instead. This is the ARIA menu pattern, and
@angular/aria/menu implements all of it — the roving focus, the keyboard navigation, and typeahead — so
Menu only has to compose the pieces.
Minimal example
[menuTrigger]="menu" wires popovertarget, aria-haspopup="menu" and a reactive aria-expanded onto the
button — no manual id to invent or keep in sync. See Pitfalls for the one thing this example
would silently break without: the #menu="menu" export form.
How it works
Menu applies two host directives rather than wrapping either: @angular/aria's Menu (roving focus,
keyboard navigation, typeahead, the itemSelected output) and this library's own PopoverDirective
(positioning, native show/close, the state signal). Selecting a non-submenu item closes the popover; opening
a submenu never bubbles into that same close path, so the root stays open while a submenu is showing.
itemSelected bound only on the root <Menu> still fires for an item selected several SubMenu levels deep —
selection bubbles all the way up, so there is never a reason to bind a handler on every nested level.
Recipes
Grouping items, with a separator
MenuContent accepts arbitrary projected content — group items visually with role="group" + a heading span,
separated by a .dialog-sep marker div (both pre-styled).
[disabled]="true" excludes an item from keyboard navigation and selection. .heading spans and role="group"
wrappers are not focusable and are skipped by the roving navigation.
Nested submenus
Nest a <SubMenu> as a sibling of the <MenuItem> that opens it — never a child — and wire the two
together with [submenu]. Submenus can be nested arbitrarily deep by repeating the pattern.
Do not add a <MenuContent> inside <SubMenu> — it is already implicit in the component's own template.
Customize a submenu's side with placement (defaults to "right-start", and flips/shifts with Floating UI
like the root popover does).
Checkable items — no control inside a menuitem
A menu entry that toggles a state cannot put a control (a checkbox, a switch) inside a MenuItem: a
menuitem containing an interactive control is invalid ARIA, and it would leave that control out of the
menu's own keyboard navigation. The ARIA APG answer is for the item itself to carry the role and the
state — supported through role="menuitemcheckbox"/"menuitemradio" plus a checked input, which also stops
the menu from closing when such an item is picked, so several options can be toggled in a row.
The state stays owned by the host: the item emits its value through itemSelected as usual, the host flips
its own signal, and checked/the projected Switch reflect it back. presentational on Switch makes its
own <input> inert — out of the keyboard path and the accessibility tree, since the MenuItem itself is
already the interactive element the switch merely illustrates.
Right-click / context menu
[menuTrigger] only sets popovertarget — the native browser auto-invoke for that attribute only applies to
<button>, so a plain element needs to open the menu imperatively, e.g. on contextmenu.
MenuComponent exposes its underlying PopoverDirective through the public popover property
(menu.popover.show()/.close()/.toggle()/.state()), which is what makes this kind of imperative control
possible.
Give another panel the Menu's look
A panel that is not a Menu — a listbox of search results, a custom popover — can wear the same surface through the
class helpers the Menu itself is built from: menuPanelVariants() (rounded, shadowed, padded panel),
menuItemVariants() (a row, with hover, active and disabled states), menuContentVariants() and
menuSeparatorVariants(). They only return class names; the behaviour — roles, keyboard, positioning — stays yours.
A row that is the current one is tinted like a hovered item: data-active="true" for the Menu's own roving items and for
the options of an @angular/aria listbox (which mark the current row that way), aria-selected="true" for a listbox written
by hand following the APG.
Options
Prop
Type
itemSelected emits the selected MenuItem's value — never emitted by an item that only opens a submenu.
Because the output is exposed through a host directive, Angular does not infer its generic type from the
template: type the handler's parameter explicitly to constrain it.
Pitfalls
What's next
Sheet
A modal panel anchored to one edge of the viewport — the drawer pattern — built on the same native <dialog> as Dialog, with its own slide-in/out animation and scroll lock.
Popover
Enhance the native HTML Popover API with Floating UI positioning — the bare primitive Menu and every other floating panel in this library are built from.