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
templateRefsorDependency 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 emitsselectedwhen 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
#templateRefsand 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 |