Dialog
The Dialog
component is a fully headless and accessible UI primitive for creating modal dialogs and overlays. It's inspired by HeadlessUI Dialog, but built natively for Angular.
It is useful for building modal windows, confirmation dialogs, forms, and other overlay content — while giving you complete control over markup and styling.
When to use it
Use Dialog
when you need a modal overlay component that is:
- Fully customizable in layout and appearance
- Accessible with screen readers and keyboard navigation
- Modal with backdrop and focus management
- Dismissible via escape key
- Compatible with complex content layouts
Common use cases:
- Confirmation dialogs
- Modal forms
- Image lightboxes
- Alert dialogs
- Settings panels
Anatomy
A complete Dialog
is composed of four components:
Dialog
- The provider and wrapper. Manages internal open/close state and modal behavior.DialogPanel
- The main content container for the dialog.DialogTitle
- The title/heading for the dialog (for accessibility).DialogDescription
- The description text for the dialog (for accessibility).
All four components are standalone and composable.
Features
- ✅ Accessible by default (ARIA attributes)
- ✅ Tailwind-friendly (no styles imposed)
- ✅ Multiple selectors supported:
<Dialog>
<div ngxDialog>
<ngx-headlessui-dialog>
- ✅ Works with both
#templateRefs
and Angular DI (DialogContextService
) - ✅ Supports multiple instances per page
- ✅ Escape key to close
- ✅ Modal behavior with
aria-modal
Installation
Dialog
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 {
DialogComponent,
DialogPanelComponent,
DialogTitleComponent,
DialogDescriptionComponent,
} from "@ngx-headless/ui";
Usage Examples
Template Reference — Basic Dialog
<Dialog #d="ngxDialog">
<div *ngIf="d.isOpen()" class="fixed inset-0 bg-black bg-opacity-50">
<DialogPanel class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg">
<DialogTitle class="text-lg font-bold">Dialog Title</DialogTitle>
<DialogDescription class="mt-2 text-gray-600">
This is the dialog content.
</DialogDescription>
<button (click)="d.close()" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded">
Close
</button>
</DialogPanel>
</div>
</Dialog>
Template Reference — Confirmation Dialog
<Dialog #confirmDialog="ngxDialog">
<div *ngIf="confirmDialog.isOpen()" class="fixed inset-0 z-50">
<!-- Backdrop -->
<div class="fixed inset-0 bg-black bg-opacity-50"></div>
<!-- Dialog -->
<div class="fixed inset-0 flex items-center justify-center p-4">
<DialogPanel class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<DialogTitle class="text-lg font-semibold text-gray-900">
Confirm Action
</DialogTitle>
<DialogDescription class="mt-2 text-sm text-gray-500">
Are you sure you want to proceed? This action cannot be undone.
</DialogDescription>
<div class="mt-6 flex gap-3 justify-end">
<button (click)="confirmDialog.close()"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200">
Cancel
</button>
<button (click)="handleConfirm(); confirmDialog.close()"
class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700">
Confirm
</button>
</div>
</DialogPanel>
</div>
</div>
</Dialog>
Accessibility
This component handles
role="dialog"
on theDialog
aria-modal="true"
when the dialog is openaria-labelledby
pointing to theDialogTitle
aria-describedby
pointing to theDialogDescription
- Keyboard interaction: Escape key to close
- Screen reader compatibility for modal state
Animations
DialogPanel
and backdrop elements can be animated freely using Angular's built-in animation system.
Example
Add animation triggers to your component:
import { trigger, transition, style, animate } from "@angular/animations";
@Component({
animations: [
trigger("fadeIn", [
transition(":enter", [
style({ opacity: 0 }),
animate("150ms ease-out", style({ opacity: 1 })),
]),
transition(":leave", [
animate("100ms ease-in", style({ opacity: 0 })),
]),
]),
trigger("slideUp", [
transition(":enter", [
style({ opacity: 0, transform: "translateY(4px) scale(0.95)" }),
animate("150ms ease-out", style({ opacity: 1, transform: "translateY(0) scale(1)" })),
]),
transition(":leave", [
animate("100ms ease-in", style({ opacity: 0, transform: "translateY(4px) scale(0.95)" })),
]),
]),
],
})
export class ExampleComponent {}
Use them in the template:
<Dialog #dialog="ngxDialog">
<div *ngIf="dialog.isOpen()" @fadeIn class="fixed inset-0 bg-black bg-opacity-50"></div>
<DialogPanel *ngIf="dialog.isOpen()" @slideUp>
Content goes here.
</DialogPanel>
</Dialog>
Component API
DialogComponent
Input | Type | Description |
---|---|---|
class | string | Class for styling wrapper |
Method | Description |
---|---|
isOpen() | Returns true if dialog open |
toggle() | Toggles dialog open/close |
open() | Opens dialog |
close() | Closes dialog |
DialogPanelComponent
- Visible only when
Dialog
is open - Can be styled or animated freely
hidden
attribute used for visibility- Supports complex content layouts
DialogTitleComponent
- Automatically sets
id="dialog-title"
for accessibility - Should be referenced by
aria-labelledby
on the dialog
DialogDescriptionComponent
- Automatically sets
id="dialog-description"
for accessibility - Should be referenced by
aria-describedby
on the dialog
Focus Management
The Dialog
component does not include built-in focus trapping. For production use, consider adding focus management:
Manual Focus Management
export class MyDialogComponent {
@ViewChild('firstFocusable') firstFocusable!: ElementRef;
@ViewChild('dialog') dialog!: DialogComponent;
openDialog() {
this.dialog.open();
// Focus first element when opened
setTimeout(() => this.firstFocusable.nativeElement.focus(), 0);
}
@HostListener('keydown.tab', ['$event'])
handleTab(event: KeyboardEvent) {
// Implement focus trapping logic here
}
}
With CDK a11y
import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';
export class MyDialogComponent implements OnDestroy {
private focusTrap?: FocusTrap;
constructor(private focusTrapFactory: FocusTrapFactory) {}
openDialog() {
this.dialog.open();
this.focusTrap = this.focusTrapFactory.create(this.dialogElement);
this.focusTrap.focusInitialElement();
}
closeDialog() {
this.dialog.close();
this.focusTrap?.destroy();
}
}
See Also
- ARIA Dialog Design Pattern
- Headless UI - Dialog
- Angular CDK a11y for focus management