Dialogs are the heaviest message surface in the @teqbench family — wider than a bottom sheet, centered in the viewport, and reserved for focused interactions: long copy, multi-step input, complex confirmation flows, or arbitrary projected content. Use a dialog when a notification or banner would be stretched past its envelope and a bottom sheet feels too constrained.
This guide assumes you already followed Install & authenticate and Configure the severity theme. The dialog package peer-depends on the severity theme and picks up the same six-tier color palette.
Install the package
npm install @teqbench/tbx-mat-dialogsProvide the dialog config at bootstrap
Dialogs need a severity-icon resolver to draw the leading tier icon. The package ships two: TbxMatDialogSeverityFontIconService (Material Symbols ligatures) and TbxMatDialogSeveritySvgIconService (inline SVGs). Wire one via the required TBX_MAT_DIALOG_PROVIDER_CONFIG injection token in app.config.ts:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideTbxMatSeverityTheme } from '@teqbench/tbx-mat-severity-theme';
import {
TBX_MAT_DIALOG_PROVIDER_CONFIG,
TbxMatDialogSeverityFontIconService,
} from '@teqbench/tbx-mat-dialogs';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideAnimationsAsync(),
provideTbxMatSeverityTheme({ invert: false }),
{
provide: TBX_MAT_DIALOG_PROVIDER_CONFIG,
useFactory: () => ({
severityIconResolverService: new TbxMatDialogSeverityFontIconService(
'material-symbols-rounded',
),
}),
},
],
});The 'material-symbols-rounded' argument picks a fontSet. If your app already provides MAT_ICON_DEFAULT_OPTIONS with a fontSet, you can drop the constructor argument and the service picks it up from there.
Import the global dialog styles in your application's stylesheet:
@use '@teqbench/tbx-mat-dialogs/styles/tbx-mat-dialogs';Fire your first dialog
Inject TbxMatDialogService and call a severity method. Each takes a config object with title and message, and returns a Promise<TbxMatDialogResult> you can await — no subscription management:
import { Component, inject } from '@angular/core';
import { TbxMatDialogService } from '@teqbench/tbx-mat-dialogs';
@Component({
selector: 'app-save-button',
template: `<button (click)="save()">Save</button>`,
})
export class SaveButtonComponent {
private readonly dialog = inject(TbxMatDialogService);
async save(): Promise<void> {
await this.dialog.success({
title: 'Saved',
message: 'Your changes are saved.',
});
}
}The other tiers work the same way:
await this.dialog.error({ title: 'Save Failed', message: 'Could not save changes.' });
await this.dialog.warning({ title: 'Caution', message: 'This may take a while.' });
await this.dialog.information({ title: 'FYI', message: 'New version available.' });
await this.dialog.help({ title: 'How it works', message: 'Tap any control for details.' });
await this.dialog.default({ title: 'Notice', message: 'Neutral surface.' });Each tier resolves its icon, panel color, and CSS class from the severity theme you wired up. The header, body, and footer share the severity background and on-severity text color.
Confirm — Yes/No flow
confirm() layers a Yes/No interaction on top of severity. The result's result field carries the dismiss reason:
import { TbxMatDialogService, TbxMatDialogDismissReason } from '@teqbench/tbx-mat-dialogs';
async onDelete(): Promise<void> {
const output = await this.dialog.confirm({
title: 'Delete Project?',
message: 'This action cannot be undone.',
});
if (output.result === TbxMatDialogDismissReason.Affirm) {
await this.deleteProject();
}
}Reasons cover every way a dialog can leave the screen: Affirm, Deny, Cancel, Close, Backdrop, Escape, ProgrammaticDismiss. Use them to branch on whether the user confirmed, denied, or backed out.
Input — projected form content
input() projects a consumer-defined component into the body. The component implements TbxMatDialogData<T> with two signals: isValid drives whether the affirm button is enabled, and value is what the dialog returns when the user confirms:
import { Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TbxMatDialogData } from '@teqbench/tbx-mat-dialogs';
@Component({
selector: 'app-rename-form',
imports: [FormsModule, MatFormFieldModule, MatInputModule],
template: `
<mat-form-field>
<input matInput cdkFocusInitial [ngModel]="name()" (ngModelChange)="name.set($event)" />
</mat-form-field>
`,
})
export class RenameFormComponent implements TbxMatDialogData<string> {
readonly name = signal('');
readonly isValid = computed(() => this.name().trim().length > 0);
readonly value = computed(() => this.name().trim());
}Open it from anywhere:
const output = await this.dialog.input<string>({
title: 'Rename',
content: RenameFormComponent,
});
if (output.result === TbxMatDialogDismissReason.Affirm) {
console.log(output.data); // typed: string
}The affirm button stays disabled until isValid returns true, so the user can't confirm an empty rename.
Collect footer values
Every method accepts a footer array of buttons and form controls. Form controls (checkbox, toggle, radio-group, toggle-group) don't dismiss — only buttons do. When a button dismisses, every control's current value is snapshotted into output.footerValues, keyed by key:
const output = await this.dialog.confirm<{ dontAskAgain: boolean }>({
title: 'Enable Notifications?',
message: 'Would you like to receive notifications for this project?',
footer: [
{ key: 'dontAskAgain', type: 'checkbox', label: "Don't ask again", align: 'start' },
{ key: 'no', type: 'button', label: 'No', result: TbxMatDialogDismissReason.Deny, align: 'end' },
{
key: 'yes',
type: 'button',
label: 'Yes',
result: TbxMatDialogDismissReason.Affirm,
emphasis: 'primary',
align: 'end',
},
],
});
if (output.result === TbxMatDialogDismissReason.Affirm) {
const dontAskAgain = output.footerValues.dontAskAgain;
}Checkbox and toggle values are boolean. Radio-group and single-select toggle-group values are string | undefined. Multi-select toggle-group values are string[].
Full control via show()
When the opinionated methods don't fit, show() takes the full configuration:
import { TbxMatSeverityLevel } from '@teqbench/tbx-mat-severity-theme';
const output = await this.dialog.show({
title: 'Custom Dialog',
icon: 'build',
type: TbxMatSeverityLevel.Warning,
subtitle: 'Optional secondary line',
contextBadge: 'Beta',
message: 'Full control over every option.',
footer: [
{ key: 'cancel', type: 'button', label: 'Cancel', result: TbxMatDialogDismissReason.Cancel, align: 'end' },
{
key: 'proceed',
type: 'button',
label: 'Proceed',
result: TbxMatDialogDismissReason.Affirm,
emphasis: 'primary',
align: 'end',
},
],
});What to try next
- Bottom sheets — for a lighter modal that anchors to the bottom edge instead of the viewport center, see Show your first bottom sheet. Same severity API and footer model; different anchor.
- Banners — for a persistent, in-flow message that doesn't take focus, see Show your first banner.
- Inversion — provide the severity theme with
invert: trueto render dialogs with the inverted palette across all tiers at once; see the end-to-end section on the severity-theme guide.