API reference
Headless Menu is the panel primitive for dropdowns and nested submenus. Pair tngMenuTrigger with tngMenu for standalone triggers, or compose with tngMenubar when you need a horizontal command strip (see the Menubar docs).
tngMenu
Hosts role="menu", tracks open state, active item, typeahead, outside dismissal, and coordinates focus with an optional trigger or parent menubar.
Trigger + menu composition
<div class="menu-shell">
<button type="button" [tngMenuTrigger]="apiCompositionFileMenu">File</button>
<div tngMenu #apiCompositionFileMenu="tngMenu" aria-label="File menu">
<button type="button" tngMenuItem tngMenuItemValue="new">New</button>
<button type="button" tngMenuItem tngMenuItemValue="open">Open</button>
</div>
</div>
| Input | Type | Details |
|---|---|---|
loop | boolean | Wraps vertical navigation at the ends of the item list. Defaults to true. |
disabled | boolean | Prevents opening the panel and clears interactive state. |
closeOnSelect | boolean | Closes the menu after a leaf selection. Defaults to true. |
dismissOnOutsideClick | boolean | Closes on pointer down outside. Defaults to true. |
dismissOnFocusout | boolean | Optional focus-leave dismissal for custom focus traps. Defaults to false. |
| Outputs | tngMenuOpened, tngMenuClosed, tngMenuSelect | tngMenuSelect carries value, itemId, and trigger ('keyboard' | 'pointer'). |
| Host | role, data-slot, data-state, hidden, aria-activedescendant | Emits data-slot="menu", data-state="open" | "closed", and uses the native hidden attribute while closed. When a row is active, the panel also updates aria-activedescendant to the active item id. |
tngMenuTrigger
Links a focusable host (commonly a button) to a tngMenu instance. The directive wires aria-controls, aria-expanded, and keyboard open/close behavior.
| Input | Type | Details |
|---|---|---|
[tngMenuTrigger] | TngMenu | null | Required reference to the menu instance (#ref="tngMenu"). |
data-slot | 'menu-trigger' | Stable hook for styling the trigger surface. |
| Host | id, aria-haspopup, aria-controls, aria-expanded | The trigger owns the accessible relationship to the linked panel and reflects open state through aria-expanded. |
| Disabled hosts | native / ARIA | Honors disabled, disabled attribute, or aria-disabled="true". |
tngMenuItem and structure helpers
Menu items participate in roving focus inside the panel. Use tngMenuGroupLabel and tngMenuSeparator for structure; submenu rows link nested tngMenu hosts with [tngMenuItemSubmenu]. Owned submenus can open by click or ArrowRight; leaf rows emit selection on click, Enter, or Space.
Nested submenu wiring
<div tngMenu #apiRootMenu="tngMenu" aria-label="Actions">
<button type="button" tngMenuItem [tngMenuItemSubmenu]="apiImportMenu">Import…</button>
<div tngMenu #apiImportMenu="tngMenu" aria-label="Import sources">
<button type="button" tngMenuItem tngMenuItemValue="csv">CSV</button>
</div>
</div>
| Directive | Key inputs | Notes |
|---|---|---|
tngMenuItem | tngMenuItemValue, tngMenuItemRole, tngMenuItemChecked, tngMenuItemSubmenu | Supports menuitem, menuitemcheckbox, and menuitemradio roles; checkbox/radio items surface aria-checked. Every item also exposes data-slot="menu-item"; active rows reflect data-active, and submenu owners reflect aria-haspopup, aria-controls, and aria-expanded. |
tngMenuBackdrop | [tngMenuBackdrop] | Optional dismiss layer that closes the linked menu on click and exposes data-slot="menu-backdrop". |
tngMenuGroupLabel, tngMenuSeparator | — | Presentation roles for labeled groups and dividers. |
Structure and backdrop
Optional tngMenuBackdrop hosts forward clicks to close an open menu—useful for modal-like overlays or dimmed shells while keeping headless markup flexible.
Backdrop closes linked menu
<section class="menu-with-backdrop">
<button type="button" [tngMenuTrigger]="apiBackdropPanel">Open</button>
<div [tngMenuBackdrop]="apiBackdropPanel" class="menu-backdrop" aria-hidden="true"></div>
<div tngMenu #apiBackdropPanel="tngMenu" aria-label="Actions">
<button type="button" tngMenuItem tngMenuItemValue="save">Save</button>
</div>
</section>
Events and focus token
Overlay-style hosts that reposition the panel after open can defer the initial focus move until coordinates are stable by providing the injection token below on the same element injector as tngMenu.
Defer initial focus until positioned
import { TNG_MENU_DEFER_HOST_FOCUS_UNTIL_POSITIONED } from '@tailng-ui/primitives';
@Component({
providers: [{ provide: TNG_MENU_DEFER_HOST_FOCUS_UNTIL_POSITIONED, useValue: true }],
// ...
})
export class MenuWithDeferredFocusComponent {}
Keyboard contract
Standalone triggers use the keys below; when a menu is owned by tngMenubar, additional horizontal navigation is handled by the menubar primitive.
Keyboard baseline
Trigger (tngMenuTrigger)
Enter / Space Toggle open; focus stays on trigger when closed
ArrowDown Open and focus first enabled item
ArrowUp Open and focus last enabled item
Escape Close when open
Open menu panel
ArrowDown / ArrowUp Move active item (respects loop)
Home / End Jump to first / last enabled item
Typeahead Matches visible item labels
Enter / Space Activate focused item (emits tngMenuSelect when applicable)
Escape Close; nested submenu closes before root
Submenus
ArrowRight Open owned submenu
ArrowLeft Close nested panel; focus returns to parent item