Primitives
A dialog is a floating window that can be used to display information or prompt the user for input.
import { Component } from "@angular/core";
import { NgpButton } from "ng-primitives/button";
import {
NgpDialog,
NgpDialogDescription,
NgpDialogOverlay,
NgpDialogTitle,
NgpDialogTrigger,
} from "ng-primitives/dialog";
@Component({
selector: "app-dialog",
imports: [
NgpButton,
NgpDialog,
NgpDialogOverlay,
NgpDialogTitle,
NgpDialogDescription,
NgpDialogTrigger,
],
template: `
<button [ngpDialogTrigger]="dialog" ngpButton>Launch Dialog</button>
<ng-template #dialog let-close="close">
<div ngpDialogOverlay>
<div ngpDialog>
<h1 ngpDialogTitle>Publish this article?</h1>
<p ngpDialogDescription>
Are you sure you want to publish this article? This action is
irreversible.
</p>
<div class="dialog-footer">
<button (click)="close()" ngpButton>Cancel</button>
<button (click)="close()" ngpButton>Confirm</button>
</div>
</div>
</div>
</ng-template>
`,
styles: `
button {
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);
}
button[data-hover] {
background-color: var(--ngp-background-hover);
}
button[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
button[data-press] {
background-color: var(--ngp-background-active);
}
[ngpDialogOverlay] {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
animation: fadeIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogOverlay][data-exit] {
animation: fadeOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog] {
background-color: var(--ngp-background);
padding: 24px;
border-radius: 8px;
box-shadow: var(--ngp-shadow);
animation: slideIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog][data-exit] {
animation: slideOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogTitle] {
font-size: 18px;
line-height: 28px;
font-weight: 600;
color: var(--ngp-text-primary);
margin: 0 0 4px;
}
[ngpDialogDescription] {
font-size: 14px;
line-height: 20px;
color: var(--ngp-text-secondary);
margin: 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 32px;
column-gap: 8px;
}
.dialog-footer [ngpButton]:last-of-type {
color: var(--ngp-text-blue);
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes slideIn {
0% {
transform: translateY(-20px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideOut {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-20px);
opacity: 0;
}
}
`,
})
export default class DialogExample {}
Import the Dialog primitives from ng-primitives/dialog
.
import {
NgpDialog,
NgpDialogTitle,
NgpDialogDescription,
NgpDialogTrigger,
NgpDialogOverlay,
} from 'ng-primitives/dialog';
Assemble the dialog directives in your template.
<button [ngpDialogTrigger]="dialog" ngpButton>Launch Dialog</button>
<ng-template #dialog let-close="close">
<div ngpDialogOverlay>
<div ngpDialog>
<h1 ngpDialogTitle>Publish this article?</h1>
<p ngpDialogDescription>
Are you sure you want to publish this article? This action is irreversible.
</p>
<div class="dialog-footer">
<button (click)="close()" ngpButton>Cancel</button>
<button (click)="close()" ngpButton>Confirm</button>
</div>
</div>
</div>
</ng-template>
Create reusable components that uses the NgpDialog
and NgpDialogTrigger
directives.
import { Component } from '@angular/core';
import { NgpButton } from 'ng-primitives/button';
import { NgpDialogTrigger } from 'ng-primitives/dialog';
import { Dialog } from './dialog';
@Component({
selector: 'app-root',
imports: [Dialog, NgpDialogTrigger, NgpButton],
template: `
<button [ngpDialogTrigger]="dialog" ngpButton>Open Dialog</button>
<ng-template #dialog let-close="close">
<app-dialog header="Dialog header">
<p>This is a dialog. It can be used to display information or to ask for confirmation.</p>
<p>
You can use the dialog to display any content you want. It can be used to display forms,
images, or any other content.
</p>
<p>
You can also use the dialog to ask for confirmation. For example, you can use it to ask
the user if they want to delete an item.
</p>
<button (click)="close()" ngpButton>Close</button>
<button (click)="close()" ngpButton>Delete</button>
</app-dialog>
</ng-template>
`,
styles: `
button {
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);
}
button[data-hover] {
background-color: var(--ngp-background-hover);
}
button[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
button[data-press] {
background-color: var(--ngp-background-active);
}
`,
})
export default class App {}
Generate a reusable dialog component using the Angular CLI.
ng g ng-primitives:primitive dialog
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
.The following directives are available to import from the ng-primitives/dialog
package:
The dialog role.
Whether the dialog is a modal.
[ngpDialog]
ngpDialog
Attribute | Description |
---|---|
data-exit |
Applied when the dialog is closing. |
[ngpDialogTitle]
ngpDialogTitle
[ngpDialogDescription]
ngpDialogDescription
The template to launch.
Whether the dialog should close on escape.
[ngpDialogTrigger]
ngpDialogTrigger
Attribute | Description |
---|---|
data-hover |
Applied when the trigger is hovered. |
data-focus-visible |
Applied when the trigger is focused. |
data-press |
Applied when the trigger is pressed. |
data-disabled |
Applied when the trigger is disabled. |
Whether the dialog should close on backdrop click.
[ngpDialogOverlay]
ngpDialogOverlay
Attribute | Description |
---|---|
data-exit |
Applied when the dialog is closing. |
Data can be passed to the dialog using the NgpDialogManager
.
import { Component, inject } from "@angular/core";
import { NgpButton } from "ng-primitives/button";
import {
injectDialogRef,
NgpDialog,
NgpDialogDescription,
NgpDialogManager,
NgpDialogOverlay,
NgpDialogTitle,
} from "ng-primitives/dialog";
@Component({
selector: "app-dialog",
imports: [NgpButton],
template: ` <button (click)="openDialog()" ngpButton>Launch Dialog</button> `,
styles: `
button {
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);
}
button[data-hover] {
background-color: var(--ngp-background-hover);
}
button[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
button[data-press] {
background-color: var(--ngp-background-active);
}
`,
})
export default class DialogDataExample {
private dialogManager = inject(NgpDialogManager);
openDialog() {
this.dialogManager.open(Dialog, {
data: "This came from the dialog opener!",
});
}
}
@Component({
imports: [
NgpButton,
NgpDialog,
NgpDialogOverlay,
NgpDialogTitle,
NgpDialogDescription,
],
template: `
<div ngpDialogOverlay>
<div ngpDialog>
<h1 ngpDialogTitle>Dialog data example</h1>
<p ngpDialogDescription>
The following value was passed to the dialog:
</p>
<p class="dialog-data">{{ dialogRef.data }}</p>
<div class="dialog-footer">
<button (click)="close()" ngpButton>Cancel</button>
<button (click)="close()" ngpButton>Confirm</button>
</div>
</div>
</div>
`,
styles: `
button {
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);
}
button[data-hover] {
background-color: var(--ngp-background-hover);
}
button[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
button[data-press] {
background-color: var(--ngp-background-active);
}
.dialog-data {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: var(--ngp-text-primary);
margin: 8px 0 0;
}
[ngpDialogOverlay] {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
animation: fadeIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogOverlay][data-exit] {
animation: fadeOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog] {
background-color: var(--ngp-background);
padding: 24px;
border-radius: 8px;
box-shadow: var(--ngp-shadow);
animation: slideIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog][data-exit] {
animation: slideOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogTitle] {
font-size: 18px;
line-height: 28px;
font-weight: 600;
color: var(--ngp-text-primary);
margin: 0 0 4px;
}
[ngpDialogDescription] {
font-size: 14px;
line-height: 20px;
color: var(--ngp-text-secondary);
margin: 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 32px;
column-gap: 8px;
}
.dialog-footer [ngpButton]:last-of-type {
color: var(--ngp-text-blue);
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes slideIn {
0% {
transform: translateY(-20px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideOut {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-20px);
opacity: 0;
}
}
`,
})
export class Dialog {
protected readonly dialogRef = injectDialogRef<string>();
close() {
this.dialogRef.close();
}
}
A drawer is a type of dialog that slides in from the side of the screen.
import { Component } from "@angular/core";
import { NgpButton } from "ng-primitives/button";
import {
NgpDialog,
NgpDialogDescription,
NgpDialogOverlay,
NgpDialogTitle,
NgpDialogTrigger,
} from "ng-primitives/dialog";
@Component({
selector: "app-dialog-drawer",
imports: [
NgpButton,
NgpDialog,
NgpDialogOverlay,
NgpDialogTitle,
NgpDialogDescription,
NgpDialogTrigger,
],
template: `
<button [ngpDialogTrigger]="drawer" ngpButton>Launch Drawer</button>
<ng-template #drawer let-close="close">
<div ngpDialogOverlay>
<div ngpDialog>
<h1 ngpDialogTitle>Settings</h1>
<p ngpDialogDescription>
Change your preferences or manage your account in this drawer.
</p>
<div class="drawer-footer">
<button (click)="close()" ngpButton>Cancel</button>
<button (click)="close()" ngpButton>Save</button>
</div>
</div>
</div>
</ng-template>
`,
styles: `
button {
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);
}
button[data-hover] {
background-color: var(--ngp-background-hover);
}
button[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
button[data-press] {
background-color: var(--ngp-background-active);
}
[ngpDialogOverlay] {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
position: fixed;
inset: 0;
display: flex;
justify-content: flex-end;
align-items: stretch;
animation: fadeIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogOverlay][data-exit] {
animation: fadeOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog] {
background-color: var(--ngp-background);
padding: 24px;
width: 320px;
max-width: 100%;
height: 100%;
box-shadow: var(--ngp-shadow);
animation: drawerSlideIn 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialog][data-exit] {
animation: drawerSlideOut 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
[ngpDialogTitle] {
font-size: 18px;
line-height: 28px;
font-weight: 600;
color: var(--ngp-text-primary);
margin: 0 0 4px;
}
[ngpDialogDescription] {
font-size: 14px;
line-height: 20px;
color: var(--ngp-text-secondary);
margin: 0;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
margin-top: 32px;
column-gap: 8px;
}
.drawer-footer [ngpButton]:last-of-type {
color: var(--ngp-text-blue);
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes drawerSlideIn {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes drawerSlideOut {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(100%);
opacity: 0;
}
}
`,
})
export default class DialogDrawerExample {}
Adheres to the WAI-ARIA design pattern.
Copyright © 2025 Angular Primitives