Primitives
A menu is a list of options or commands presented to the user in a dropdown list.
import { Component } from "@angular/core";
import { NgpButton } from "ng-primitives/button";
import { NgpMenu, NgpMenuItem, NgpMenuTrigger } from "ng-primitives/menu";
@Component({
selector: "app-menu",
imports: [NgpButton, NgpMenu, NgpMenuTrigger, NgpMenuItem],
template: `
<button [ngpMenuTrigger]="menu" ngpButton>Open Menu</button>
<ng-template #menu>
<div ngpMenu>
<button ngpMenuItem>Item 1</button>
<button ngpMenuItem>Item 2</button>
<button ngpMenuItem>Item 3</button>
</div>
</ng-template>
`,
styles: `
[ngpButton] {
padding-left: 1rem;
padding-right: 1rem;
border-radius: 0.5rem;
color: var(--ngp-text-primary);
border: none;
outline: none;
height: 2.5rem;
font-weight: 500;
background-color: var(--ngp-background);
transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--ngp-button-shadow);
}
[ngpButton][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpButton][data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
}
[ngpButton][data-press] {
background-color: var(--ngp-background-active);
}
[ngpMenu] {
position: fixed;
display: flex;
flex-direction: column;
width: max-content;
background: var(--ngp-background);
border: 1px solid var(--ngp-border);
box-shadow: var(--ngp-shadow-lg);
border-radius: 8px;
padding: 4px;
transform-origin: var(--ngp-menu-transform-origin);
}
[ngpMenu][data-enter] {
animation: menu-show 0.1s ease-out;
}
[ngpMenu][data-exit] {
animation: menu-hide 0.1s ease-out;
}
[ngpMenu][data-enter] {
animation: menu-show 0.1s ease-out;
}
[ngpMenu][data-exit] {
animation: menu-hide 0.1s ease-out;
}
[ngpMenuItem] {
padding: 6px 14px;
border: none;
background: none;
cursor: pointer;
transition: background-color 0.2s;
border-radius: 4px;
min-width: 120px;
text-align: start;
outline: none;
font-size: 14px;
font-weight: 400;
}
[ngpMenuItem][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpMenuItem][data-press] {
background-color: var(--ngp-background-active);
}
[ngpMenuItem][data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
z-index: 1;
}
@keyframes menu-show {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes menu-hide {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.9);
}
}
`,
})
export default class MenuExample {}
Import the Menu primitives from ng-primitives/menu
.
import { NgpMenu, NgpMenuItem, NgpMenuTrigger, NgpSubmenuTrigger } from 'ng-primitives/menu';
Assemble the menu directives in your template.
<button [ngpMenuTrigger]="menu" ngpButton></button>
<ng-template #menu>
<div ngpMenu>
<button ngpMenuItem>Item 1</button>
<button ngpMenuItem>Item 2</button>
<button ngpMenuItem>Item 3</button>
</div>
</ng-template>
Create reusable components that use the NgpMenu
directive.
import { Component } from '@angular/core';
import { NgpButton } from 'ng-primitives/button';
import { NgpMenuTrigger } from 'ng-primitives/menu';
import { Menu } from './menu';
import { MenuItem } from './menu-item';
@Component({
selector: 'app-root',
imports: [Menu, NgpButton, NgpMenuTrigger, MenuItem],
template: `
<button [ngpMenuTrigger]="menu" ngpButton>Open Menu</button>
<ng-template #menu>
<app-menu>
<button app-menu-item>Item 1</button>
<button app-menu-item>Item 2</button>
<button app-menu-item>Item 3</button>
</app-menu>
</ng-template>
`,
styles: `
[ngpButton] {
padding-left: 1rem;
padding-right: 1rem;
border-radius: 0.5rem;
color: var(--ngp-text-primary);
border: none;
outline: none;
height: 2.5rem;
font-weight: 500;
background-color: var(--ngp-background);
transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--ngp-button-shadow);
}
[ngpButton][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpButton][data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
[ngpButton][data-press] {
background-color: var(--ngp-background-active);
}
`,
})
export default class App {}
Generate a reusable menu component using the Angular CLI.
ng g ng-primitives:primitive menu
path
: The path at which to create the component file.prefix
: The prefix to apply to the generated component selector.componentSuffix
: The suffix to apply to the generated component class name.fileSuffix
: The suffix to apply to the generated component file name. Defaults to component
.exampleStyles
: Whether to include example styles in the generated component file. Defaults to true
.Here are some additional examples of how to use the Menu primitives.
The menu can contain submenus, which are nested menus that can be opened by hovering on a menu item.
import { Component } from "@angular/core";
import { NgIcon, provideIcons } from "@ng-icons/core";
import { heroChevronRightMini } from "@ng-icons/heroicons/mini";
import { NgpButton } from "ng-primitives/button";
import {
NgpMenu,
NgpMenuItem,
NgpMenuTrigger,
NgpSubmenuTrigger,
} from "ng-primitives/menu";
@Component({
selector: "app-menu-submenu",
imports: [
NgpButton,
NgpMenu,
NgpMenuTrigger,
NgpMenuItem,
NgpSubmenuTrigger,
NgIcon,
],
providers: [provideIcons({ heroChevronRightMini })],
template: `
<button [ngpMenuTrigger]="menu" ngpButton>Open Menu</button>
<ng-template #menu>
<div ngpMenu>
<button ngpMenuItem>Item 1</button>
<button ngpMenuItem>Item 2</button>
<button [ngpSubmenuTrigger]="submenu" ngpMenuItem>
Item 3
<ng-icon name="heroChevronRightMini" />
</button>
</div>
</ng-template>
<ng-template #submenu>
<div ngpMenu>
<button ngpMenuItem>Item 1</button>
<button ngpMenuItem>Item 2</button>
<button ngpMenuItem>Item 3</button>
</div>
</ng-template>
`,
styles: `
[ngpButton] {
padding-left: 1rem;
padding-right: 1rem;
border-radius: 0.5rem;
color: var(--ngp-text-primary);
border: none;
outline: none;
height: 2.5rem;
font-weight: 500;
background-color: var(--ngp-background);
transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--ngp-button-shadow);
}
[ngpButton][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpButton][data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
}
[ngpButton][data-press] {
background-color: var(--ngp-background-active);
}
[ngpMenu] {
position: fixed;
display: flex;
flex-direction: column;
width: max-content;
background: var(--ngp-background);
border: 1px solid var(--ngp-border);
box-shadow: var(--ngp-shadow-lg);
border-radius: 8px;
padding: 4px;
animation: menu-show 0.2s ease-out;
transform-origin: var(--ngp-popover-transform-origin);
}
[ngpMenu][data-exit] {
animation: menu-hide 0.2s ease-out;
}
[ngpMenuItem] {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px 6px 14px;
border: none;
background: none;
cursor: pointer;
transition: background-color 0.2s;
border-radius: 4px;
min-width: 120px;
text-align: start;
outline: none;
font-size: 14px;
font-weight: 400;
}
[ngpMenuItem][data-hover] {
background-color: var(--ngp-background-hover);
}
[ngpMenuItem][data-press] {
background-color: var(--ngp-background-active);
}
[ngpMenuItem][data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
z-index: 1;
}
@keyframes menu-show {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes menu-hide {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.9);
}
}
`,
})
export default class MenuExample {}
The following directives are available to import from the ng-primitives/menu
package:
The `NgpMenuTrigger` directive allows you to turn an element into a menu trigger. Access the menu template ref.
Define if the trigger should be disabled.
Define the placement of the menu relative to the trigger.
Define the offset of the menu relative to the trigger.
Define whether the menu should flip when there is not enough space for the menu.
Define the container in which the menu should be attached.
Defines how the menu behaves when the window is scrolled.
Provide context to the menu. This can be used to pass data to the menu content.
[ngpMenuTrigger]
ngpMenuTrigger
The following data attributes are available on the NgpMenuTrigger
directive:
Attribute | Description |
---|---|
data-open |
Applied when the menu is open. |
The `NgpMenu` is a container for menu items.[ngpMenu]
ngpMenu
The following CSS custom properties are applied to the ngpMenu
directive:
Property | Description |
---|---|
--ngp-menu-transform-origin |
The transform origin of the menu for animations. |
--ngp-menu-trigger-width |
The width of the trigger element. |
The `NgpMenuItem` directive represents a menu item.[ngpMenuItem]
ngpMenuItem
The following data attributes are available on the NgpMenuItem
directive:
Attribute | Description |
---|---|
data-disabled |
Applied when the item is disabled. |
Access the submenu template ref.
Define if the trigger should be disabled.
Define the placement of the menu relative to the trigger.
Define the offset of the menu relative to the trigger.
Define whether the menu should flip when there is not enough space for the menu.
[ngpSubmenuTrigger]
ngpSubmenuTrigger
The following data attributes are available on the NgpSubmenuTrigger
directive:
Attribute | Description |
---|---|
data-open |
Applied when the submenu is open. |
For the menu to be positioned correctly relative to the trigger element, it should use fixed positioning. For example, you can use the following CSS:
[ngpMenu] {
position: fixed;
}
The ngpMenu
primitive adds a CSS custom property --ngp-menu-transform-origin
to the element that can be used to animate the menu from the trigger element.
The ngpMenu
will also add the data-enter
and data-exit
attributes to the element when it is being added or removed from the DOM. This can be used to trigger animations.
:host[data-enter] {
animation: fade-in 0.2s ease-in-out;
}
:host[data-exit] {
animation: fade-out 0.2s ease-in-out;
}
You can configure the default options for all menus in your application by using the provideMenuConfig
function in a providers array.
import { provideMenuConfig } from 'ng-primitives/menu';
bootstrapApplication(AppComponent, {
providers: [
provideMenuConfig({
offset: 4,
placement: 'top',
flip: true,
container: document.body,
scrollBehavior: 'reposition',
}),
],
});
Define the offset from the trigger element.
Define the placement of the menu.
Define if the menu should flip when it reaches the edge of the viewport.
Define the container element for the menu. This is the document body by default.
Defines how the menu behaves when the window is scrolled. If set to `reposition`, the menu will adjust its position automatically during scrolling. Make sure the menu uses `position: absolute` in this mode. If set to `block`, scrolling will be disabled while the menu is open. In this case, the menu should use `position: fixed`.
Adhere to the WAI-ARIA Authoring Practices for menus and submenus.
Copyright © 2025 Angular Primitives