Base UI (Angular) dialogs are modals you open with DialogService.open(), not a router outlet. The CLI copies the overlay, focus trap, and header/body/footer pieces into your app. On the server, open() is a no-op so prerender stays SSR-safe. Close with a typed result via DialogContext. The dialog box sets view-transition-name: base-dialog for the View Transitions API.
Install with npx base-ui-cli add dialog.
Basic Dialog example below. Wrap DialogService.open() in document.startViewTransition() — the box already has view-transition-name: base-dialog. Recipe: View Transitions cookbook.
<base-dialog width="400">
<base-dialog-header>
Test Header
<button base-icon-button color="transparent" base-dialog-close>
<base-icon name="x"></base-icon>
</button>
</base-dialog-header>
<base-dialog-body>
With less than a month to go before the European Union enacts new consumer
privacy laws for its citizens, companies around the world are updating their
terms of service agreements to comply.
</base-dialog-body>
<base-dialog-footer>
<button base-stroked-button base-dialog-close>Cancel</button>
<button base-button color="primary" base-dialog-close>Save</button>
</base-dialog-footer>
</base-dialog>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-dialog-example',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class DialogExampleComponent {
private dialog = inject(DialogService);
}Install
npx base-ui-cli add dialog
Dialog containing form elements and date pickers to gather information.
<base-dialog width="500">
<base-dialog-header>Schedule Appointment</base-dialog-header>
<base-dialog-body>
<base-input-group>
<base-label>Full Name</base-label>
<input base-input placeholder="Enter your name">
</base-input-group>
<base-datepicker label="Birth Date"></base-datepicker>
</base-dialog-body>
<base-dialog-footer>
<button base-stroked-button base-dialog-close>Cancel</button>
<button base-button color="primary" base-dialog-close>Save</button>
</base-dialog-footer>
</base-dialog>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-dialog-example',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class DialogExampleComponent {
private dialog = inject(DialogService);
}Install
npx base-ui-cli add dialog
Use DialogService.confirm() for destructive or important actions. It emits true only when the user confirms; Cancel, Escape, and backdrop click emit false.
this.dialog.confirm({
title: 'Delete this item?',
description: 'This cannot be undone.',
destructive: true,
}).subscribe((ok) => {
if (ok) remove();
});TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-dialog-example',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class DialogExampleComponent {
private dialog = inject(DialogService);
}Install
npx base-ui-cli add dialog
Dialogs with long content automatically enable scrolling in the body while keeps header and footer fixed. Use the height property to set a fixed height or a large enough value for full-screen feel.
<base-dialog width="600" height="600">
<base-dialog-header>Terms and Conditions</base-dialog-header>
<base-dialog-body>
<!-- Long content here -->
</base-dialog-body>
<base-dialog-footer>
<button base-stroked-button base-dialog-close>Decline</button>
<button base-button color="primary" base-dialog-close>Accept</button>
</base-dialog-footer>
</base-dialog>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-dialog-example',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class DialogExampleComponent {
private dialog = inject(DialogService);
}Install
npx base-ui-cli add dialog
While a mutation is in flight, swap the confirm action for base-progress-button and disable dismiss if rollback is unsafe.
<button base-stroked-button disabled>Cancel</button> <base-progress-button color="primary" [loading]="saving()">Save</base-progress-button>
TypeScript
import { Component } from '@angular/core';
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-dialog-example',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class DialogExampleComponent {
private dialog = inject(DialogService);
}Install
npx base-ui-cli add dialog
Site-wide VPAT-style notes live on the accessibility report.
Unit tests: copy DialogHarness with the component and load it with TestbedHarnessEnvironment.documentRootLoader(fixture) (the overlay is appended to document.body). See Getting started — Testing.
Run the following command to add this component to your project:
npx base-ui-cli add dialog
For AI agents
Copy a prompt with the registry name, CLI install, and import — or add the Base UI MCP server.
npx -y base-ui-ng-mcp
Base UI provides standalone components. Import the exact elements you want to use into your component.
// Install: npx base-ui-cli add dialog
// Paths are relative to aliases.components (default: src/app/components)
import { DialogBodyComponent } from './dialog/dialog-body/dialog-body.component';
import { DialogCloseDirective } from './dialog/dialog-close.directive';
import { DialogContainer } from './dialog/dialog-container';
import { DialogService } from './dialog/dialog.service';
import { DialogComponent } from './dialog/dialog/dialog.component';
@Component({
selector: 'app-your-component',
imports: [
DialogBodyComponent,
DialogCloseDirective,
DialogComponent
],
template: `...`
})
export class YourComponent {
private dialog = inject(DialogService);
} API for dialog — generated from JSDoc in the library source.
| API | Member | Type | Default | Description |
|---|---|---|---|---|
DialogService | open() | (type: Type<unknown>, data?: TData, className?: string, options: { hideOnBackdropClick?: boolean; containerType?: Type<DialogContainer> } = {}) => Observable<TResult | undefined> | — | Opens a component dynamically inside a dialog container. On the server this returns `of(undefined)` and does not touch the DOM. |
DialogService | confirm() | (options: { title: string; description?: string; confirmLabel?: string; cancelLabel?: string; destructive?: boolean; }) => Observable<boolean> | — | Opens a confirm / alert dialog and emits `true` only when the user confirms. Backdrop, Escape, and Cancel emit `false`. SSR-safe: emits `false` on the server and does not touch the DOM. |
DialogContainer | context | DialogContext<unknown, unknown> | — | The dialog context injected into the hosted component. |
DialogContainer | container | Signal<ViewContainerRef> | — | The ViewContainerRef where the dialog content is dynamically rendered. |
DialogContainer | className | string | — | Optional CSS class string applied to the container wrapper. |
base-dialog | width | number | string | — | Explicit width: pixels as a number or any CSS width string (e.g. `'640'`, `'48rem'`). |
base-dialog | height | number | — | Explicit height in pixels. Applied dynamically to the `base-dialog-body` for scrolling. |
base-dialog-body | height | number | — | — |
[base-dialog-close] | ariaLabel | string | — | Optional aria-label for accessibility. Falls back to `provideBaseUiI18n().close`. |
[base-dialog-close] | type | unknown | 'button' | The native button type. Defaults to 'button'. |
Content