Menu
The Menu
component is a fully headless and accessible dropdown menu primitive. It lets you build custom context menus, user dropdowns, and more — with full control over markup, behavior, and transitions.
Inspired by Headless UI Menu, but built for Angular.
When to use it
Use Menu
when you need a dropdown menu that gives you full control over:
- Markup and structure — no wrapper
<ul>
or forced elements - Styling — works perfectly with Tailwind, Bootstrap, or any custom design system
- Behavior — full support for keyboard navigation, focus management, and accessibility
- Logic — control opening/closing manually or through templates
- Controlled via Angular
templateRefs
orDependency Injection (DI)
Common use cases:
- Profile dropdowns
- Action menus (e.g. "Edit", "Duplicate", "Delete")
- Filter or sort menus
- Context menus (right-click or dot menus)
Anatomy
A complete Disclosure
is composed of three components:
Menu
- provides shared context for all children.MenuButton
- toggles the menu open/closed.MenuItems
- contains all the interactive options.MenuItem
- is an actionable item that emitsselected
when clicked
Each element is fully composable and unstyled — you control layout, transitions, icons, and interaction patterns.
Features
- ✅ Accessible by default (ARIA attributes)
- ✅ Tailwind-friendly (no styles imposed)
- ✅ Multiple selectors supported:
<Menu>
<div ngxMenu>
<ngx-headlessui-menu>
- ✅ Works with both
#templateRefs
and Angular DI (MenuContextService
) - ✅ Supports multiple instances per page
Installation
Menu
ships as part of the @ngx-headless/ui
by default. Install if you haven't already.
npm install @ngx-headless/ui
Import the components directly:
import {
MenuComponent,
MenuButtonComponent,
MenuItemsComponent,
MenuItemComponent
} from "@ngx-headless/ui";
Usage Examples
Template Reference — Single Menu
<Menu #menuRef="ngxMenu">
<MenuButton [class.active]="menuRef.isOpen()">Toggle</MenuButton>
<MenuItems *ngIf="menuRef.isOpen()">
<MenuItem>...</MenuItem>
</MenuItems>
</Menu>
Template Reference — Multiple Menus (ngFor)
<Menu *ngFor="let action of actions" #menuRef="ngxMenu">
<MenuButton [class.active]="menuRef.isOpen()">Toggle</MenuButton>
<MenuItems *ngIf="menuRef.isOpen()">
<MenuItem *ngFor="let item of action.items" > {{item.label}} </MenuItem>
</MenuItems>
</Menu>
Access all instances in the component in your class Root:
...
export class MyComponent {
...
@ViewChildren('menuRef') menuInstances!: QueryList<MenuComponent>;
// ☝️ access to All Menus via #templateRefs
// 🧠 NOTE: You can also use @ViewChild with MenuComponent without QueryList<> to capture a single instance
...
}
Accessibility
- Fully ARIA-compliant with roles:
menu
,menuitem
,aria-haspopup
,aria-expanded
- Keyboard support (coming soon)
- Screen reader-friendly structure
Animations
MenuItems
can be animated freely using Angular’s built-in animation system.
Angular supports entry/exit transitions using @trigger
bindings and the :enter
/:leave
lifecycle hooks.
👉 See Angular Animations Guide
Example
Add an animation trigger to your component:
import { trigger, transition, style, animate } from "@angular/animations";
@Component({
animations: [
trigger("menuAppear", [
transition(':enter', [
style({ opacity: 0, transform: 'scale(0.9)' }),
animate('100ms ease-out', style({ opacity: 1, transform: 'scale(1)' }))
]),
transition(':leave', [
style({ opacity: 1, transform: 'scale(1)' }),
animate('75ms ease-in', style({ opacity: 0, transform: 'scale(0.9)' }))
])
]),
],
})
export class ExampleComponent {}
Use it in the template with *ngIf
:
<MenuItems *ngIf="disclosure.isOpen()" @menuAppear>
Panel content goes here.
</MenuItems>
✅ You now have full control over the entrance and exit behavior of the panel.
You can combine this with Tailwind
transitions or any styling framework as needed.
Component API
MenuComponent
Input | Type | Description |
---|---|---|
class | string | Class applied to the wrapper |
Host Binding | Description |
---|---|
role="menu" | ARIA role |
aria-orientation="vertical" | Indicates vertical layout |
aria-expanded | Reflects open state |
Behavior | Description |
---|---|
Auto close | Closes menu when clicking outside |
MenuButtonComponent
Input | Type | Description |
---|---|---|
class | string | Class applied to the button |
Host Binding | Description |
---|---|
role="button" | ARIA role |
aria-haspopup="menu" | Indicates menu relationship |
aria-expanded | Reflects open state |
tabindex="0" | Makes the element focusable |
Behavior | Description |
---|---|
(click) toggles menu | Toggles menu open state |
MenuItemsComponent
Input | Type | Description |
---|---|---|
class | string | Class applied to the menu items container |
Host Binding | Description |
---|---|
role="menu" | ARIA role |
hidden | Hidden when Menu is closed |
MenuItemComponent
Input | Type | Description |
---|---|---|
class | string | Class applied to the menu item |
disabled | boolean | Prevents interaction if true |
Output | Type | Description |
---|---|---|
selected | EventEmitter<void> | Emits on item click (if enabled) |
Host Binding | Description |
---|---|
role="menuitem" | ARIA role |
tabindex="-1" | Makes item keyboard-accessible |
aria-disabled | Reflects disabled state |