Primitives
Display arbitrary content inside floating panels.
Import the Popover primitives from ng-primitives/popover.
import { NgpPopover, NgpPopoverTrigger, NgpPopoverArrow } from 'ng-primitives/popover';
Assemble the popover directives in your template.
<button [ngpPopoverTrigger]="popover" (ngpPopoverTriggerOpenChange)="onPopoverStateChange($event)">
Click me
</button>
<ng-template #popover>
<div ngpPopover>Popover content</div>
</ng-template>
You can listen to the ngpPopoverTriggerOpenChange event to perform actions when the popover state changes. The event emits a boolean value indicating whether the popover is open or closed:
Create a popover component that uses the NgpPopover directive.
Generate a reusable popover component using the Angular CLI.
ng g ng-primitives:primitive popover
path: The path at which to create the component file.prefix: The prefix to apply to the generated component selector.component-suffix: The suffix to apply to the generated component class name.file-suffix: The suffix to apply to the generated component file name. Defaults to component.styles: How component styles should be generated. css (default) includes the full example styles; unstyled omits them entirely so you can style the component yourself.example-styles (deprecated): still supported for compatibility - true maps to styles: css, false maps to styles: unstyled.You can customize the offset using either a simple number or an object for more precise control:
<!-- Simple number offset -->
<button [ngpPopoverTrigger]="popover" ngpPopoverTriggerOffset="12">Popover with 12px offset</button>
<!-- Object offset for precise control -->
<button
[ngpPopoverTrigger]="popover"
[ngpPopoverTriggerOffset]="{mainAxis: 8, crossAxis: 4, alignmentAxis: 2}"
>
Popover with custom offset
</button>
You can customize the shift behavior to control how the popover stays within the viewport:
<!-- Disable shift -->
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerShift]="false">
Popover without shift
</button>
<!-- Object shift for precise control -->
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerShift]="{padding: 8}">
Popover with custom shift padding
</button>
By default, the popover is kept within the viewport and its clipping ancestors. Pass boundary or rootBoundary to flip or shift to measure overflow against something else:
<!-- Keep the popover inside a container element -->
<div #boundary>
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerFlip]="{boundary: boundary}">
Popover constrained to a container
</button>
</div>
<!-- Keep the popover inside whatever scrolls the trigger -->
<div style="overflow: auto">
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerFlip]="{altBoundary: true}">
Popover constrained to the trigger's scroll container
</button>
</div>
<!-- Measure against the whole document rather than the viewport -->
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerFlip]="{rootBoundary: 'document'}">
Popover that does not flip while off-screen
</button>
altBoundary measures against the trigger's clipping ancestors rather than the popover's. The popover is portalled to the body, so its own clipping ancestors are effectively the viewport - set this when the trigger sits in a scroll container. It applies only while boundary is left at its default; an explicit boundary always wins.
crossAxis widens the axis each option checks. For flip it is the alignment axis, on by default - that is how bottom-end becomes bottom-start near an edge. For shift it is the side axis, off by default; enabling it lets the panel move along the placement direction too:
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerFlip]="{crossAxis: false}">
Keep the alignment
</button>
<button [ngpPopoverTrigger]="popover" [ngpPopoverTriggerShift]="{crossAxis: true}">
Shift on both axes
</button>
The --ngp-popover-available-width and --ngp-popover-available-height custom properties are measured against the flip boundary, or the shift boundary when flip does not set one.
The popover can be anchored to a different element than the trigger.
The anchor is live - rebinding it moves an already-open popover to the new element, and it is the new element that counts as inside for dismissal.
Use dismiss guards to prevent a popover from closing when there are unsaved changes. The closeOnOutsideClick and closeOnEscape options accept a guard function that returns a boolean or a Promise<boolean>.
By default, the popover content is destroyed when the popover closes and created again on the next open, so any one-time setup runs every time. Set ngpPopoverTriggerKeepMounted to keep the content instance alive between opens instead.
The content is still removed from the DOM while the popover is closed, so nothing is left rendered behind the scenes. What survives is the component instance and its DOM nodes: the content is not re-instantiated, so one-time work (such as a network request) is not repeated and unsaved state (such as a partly filled form or a scroll position) is preserved.
Two things to weigh before enabling it:
ngpPopoverTrigger content changes, so anything expensive it holds on to stays in memory.The following directives are available to import from the ng-primitives/popover package:
The NgpPopoverArrow directive is used to add an arrow to the popover. It should be placed inside the popover content. It will receive inset-inline-start or inset-block-start styles to position the arrow based on the popover's placement. As a result it should be positioned absolutely within the popover content.
The arrow can be styled conditionally based on the popover's final placement using the data-placement attribute:
[ngpPopoverArrow][data-placement='top'] {
/* Arrow styles when popover is positioned on top */
}
[ngpPopoverArrow][data-placement='bottom'] {
/* Arrow styles when popover is positioned on bottom */
}
For the popover to be positioned correctly relative to the trigger element, it must use absolute or fixed positioning. For example, you can use the following CSS:
[ngpPopover] {
position: absolute;
}
The ngpPopover primitive adds a CSS custom property --ngp-popover-transform-origin to the element that can be used to animate the popover from the trigger element.
The ngpPopover 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 popovers in your application by using the providePopoverConfig function in a providers array.
import { providePopoverConfig } from 'ng-primitives/popover';
bootstrapApplication(AppComponent, {
providers: [
providePopoverConfig({
offset: 4,
placement: 'top',
showDelay: 0,
hideDelay: 0,
flip: true,
container: document.body,
closeOnOutsideClick: true,
scrollBehavior: 'reposition',
cooldown: 0,
keepMounted: false,
}),
],
});
The popover element is assigned role="dialog" and the trigger element uses aria-expanded to indicate the popover's open state. The trigger is linked to the popover via aria-describedby. Focus is trapped within the popover when open.
Copyright © 2026 Angular Primitives
This site is powered by Netlify