Dialog

Show content in a centered panel over a dimmed backdrop. Tapping the backdrop closes it.

Installation

npx @blotch/dolan add dialog
import {
  UiDialog,
  UiDialogHeader,
  UiDialogTitle,
  UiDialogDescription,
  UiDialogFooter,
} from '../components/ui/dialog';

Usage

import { Component, signal } from '@angular/core';
import {
  UiDialog,
  UiDialogHeader,
  UiDialogTitle,
  UiDialogDescription,
  UiDialogFooter,
} from '../components/ui/dialog';
import { UiButton } from '../components/ui/button';

@Component({
  selector: 'app-edit-profile',
  imports: [
    UiDialog,
    UiDialogHeader,
    UiDialogTitle,
    UiDialogDescription,
    UiDialogFooter,
    UiButton,
  ],
  template: `
    <ui-button (pressed)="open.set(true)">Edit profile</ui-button>

    <ui-dialog [(open)]="open">
      <ui-dialog-header>
        <ui-dialog-title>Edit profile</ui-dialog-title>
        <ui-dialog-description>
          Make changes to your profile here.
        </ui-dialog-description>
      </ui-dialog-header>
      <ui-dialog-footer>
        <ui-button variant="outline" (pressed)="open.set(false)">
          Cancel
        </ui-button>
        <ui-button (pressed)="open.set(false)">Save</ui-button>
      </ui-dialog-footer>
    </ui-dialog>
  `,
})
export class EditProfile {
  protected readonly open = signal(false);
}
  • [(open)] two-way binds a signal. Set it to true to open the dialog and false to close it.
  • A backdrop tap sets open back to false. Taps inside the panel don't reach the backdrop.
  • The backdrop fades in and the panel scales up. Closing plays the reverse.
  • The dialog draws in an <overlay>, so it covers the whole screen wherever it sits in your template.
  • <ui-dialog-title> and <ui-dialog-description> render <text>, so put plain text in them. The footer lays out its buttons in a row, aligned right.

Run code after it closes

closed emits once the close animation has finished, about 270ms after open turns false. It fires from a timer, outside any tap handler, so it's a safe place to navigate.

// <ui-dialog [(open)]="open" (closed)="onClosed()">
readonly #router = inject(Router);

protected onClosed(): void {
  this.#router.navigateByUrl('/profile');
}

To navigate straight from a button in the dialog instead, wrap the navigation in setTimeout. See the setTimeout rule for why.

Other content

Put any elements between the header and footer, such as a form. Import LYNX_ELEMENTS in your component for them.

<ui-dialog [(open)]="open">
  <ui-dialog-header>
    <ui-dialog-title>Rename</ui-dialog-title>
  </ui-dialog-header>
  <view class="py-4">
    <text class="text-sm text-foreground">Name: John Doe</text>
  </view>
</ui-dialog>

Panel width

class goes on the panel, which is w-4/5 wide by default.

<ui-dialog [(open)]="open" class="w-11/12">…</ui-dialog>

Make the user choose

A backdrop tap lets the user leave without choosing. When they must pick an action, such as confirming a delete, use Alert Dialog instead.

API

UiDialog

InputTypeDefaultDescription
openbooleanfalseWhether the dialog is shown. Supports [(open)].
classstring''Extra Tailwind classes for the panel.
OutputTypeDescription
openChangebooleanEmits false when a backdrop tap closes the dialog. Part of [(open)].
closedvoidEmits after the close animation finishes.

All sub-components (UiDialogHeader, UiDialogTitle, UiDialogDescription, UiDialogFooter) accept class.