AvesraAvesrabeta

Alert Dialog

Modal dialog for critical confirmations requiring user attention and explicit action

Import

import {
  AvAlertDialogImports,
  AvAlertDialogService,
} from '@avesra/angular';

Usage

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-basic-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog>
      <button av-button variant="danger" av-alert-dialog-trigger>Delete Project</button>
      <ng-template avAlertDialogContent>
        <div av-alert-dialog-dialog class="sm:max-w-[400px]">
          <av-alert-dialog-close-trigger />
          <div av-alert-dialog-header>
            <div av-alert-dialog-icon status="danger"></div>
            <h2 av-alert-dialog-heading>Delete project permanently?</h2>
          </div>
          <div av-alert-dialog-body>
            <p>
              This will permanently delete <strong>My Awesome Project</strong> and all of its
              data. This action cannot be undone.
            </p>
          </div>
          <div av-alert-dialog-footer>
            <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
            <button av-button variant="danger" av-alert-dialog-close>Delete Project</button>
          </div>
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogBasicDemo {}

Anatomy

<av-alert-dialog>
  <button av-button av-alert-dialog-trigger>Open Alert Dialog</button>
  <ng-template avAlertDialogContent>
    <div av-alert-dialog-dialog>
      <!-- Optional: Close button -->
      <av-alert-dialog-close-trigger />
      <div av-alert-dialog-header>
        <!-- Optional: Status icon -->
        <div av-alert-dialog-icon></div>
        <h2 av-alert-dialog-heading></h2>
      </div>
      <div av-alert-dialog-body></div>
      <div av-alert-dialog-footer></div>
    </div>
  </ng-template>
</av-alert-dialog>

Examples

Statuses

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import type { AvAlertDialogIconStatus, AvButtonVariant } from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-statuses-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex flex-wrap gap-4">
      @for (example of statusExamples; track example.status) {
        <av-alert-dialog>
          <button av-button [style]="example.triggerStyle" av-alert-dialog-trigger>
            {{ example.trigger }}
          </button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon [status]="example.status"></div>
                <h2 av-alert-dialog-heading>{{ example.header }}</h2>
              </div>
              <div av-alert-dialog-body>
                <p>{{ example.body }}</p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>
                  {{ example.cancel }}
                </button>
                <button
                  av-button
                  [variant]="example.confirmVariant"
                  av-alert-dialog-close
                >
                  {{ example.confirm }}
                </button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      }
    </div>`,
})
export class AlertDialogStatusesDemo {
  readonly statusExamples: {
    status: AvAlertDialogIconStatus;
    trigger: string;
    header: string;
    body: string;
    classNames: string;
    triggerStyle: Record<string, string>;
    cancel: string;
    confirm: string;
    confirmVariant: AvButtonVariant;
  }[] = [
    {
      status: 'accent',
      trigger: 'Sign Out',
      header: 'Sign out of your account?',
      body: "You'll need to sign in again to access your account. Any unsaved changes will be lost.",
      classNames: 'bg-accent-soft text-accent-soft-foreground',
      triggerStyle: {
        '--av-button-bg': 'var(--av-accent-soft)',
        '--av-button-bg-hover': 'var(--av-accent-soft-hover)',
        '--av-button-bg-pressed': 'var(--av-accent-soft-hover)',
        '--av-button-fg': 'var(--av-accent-soft-foreground)',
      },
      cancel: 'Stay Signed In',
      confirm: 'Sign Out',
      confirmVariant: 'primary',
    },
    {
      status: 'success',
      trigger: 'Complete Task',
      header: 'Complete this task?',
      body: 'This will mark the task as complete and notify all team members. The task will be moved to your completed list.',
      classNames: 'bg-success-soft text-success-soft-foreground',
      triggerStyle: {
        '--av-button-bg': 'var(--av-success-soft)',
        '--av-button-bg-hover': 'var(--av-success-soft-hover)',
        '--av-button-bg-pressed': 'var(--av-success-soft-hover)',
        '--av-button-fg': 'var(--av-success-soft-foreground)',
      },
      cancel: 'Not Yet',
      confirm: 'Mark Complete',
      confirmVariant: 'primary',
    },
    {
      status: 'warning',
      trigger: 'Discard Changes',
      header: 'Discard unsaved changes?',
      body: 'You have unsaved changes that will be permanently lost. Are you sure you want to discard them?',
      classNames: 'bg-warning-soft text-warning-soft-foreground',
      triggerStyle: {
        '--av-button-bg': 'var(--av-warning-soft)',
        '--av-button-bg-hover': 'var(--av-warning-soft-hover)',
        '--av-button-bg-pressed': 'var(--av-warning-soft-hover)',
        '--av-button-fg': 'var(--av-warning-soft-foreground)',
      },
      cancel: 'Keep Editing',
      confirm: 'Discard',
      confirmVariant: 'primary',
    },
    {
      status: 'danger',
      trigger: 'Delete Account',
      header: 'Delete your account?',
      body: 'This will permanently delete your account and remove all your data from our servers. This action is irreversible.',
      classNames: 'bg-danger-soft text-danger-soft-foreground',
      triggerStyle: {
        '--av-button-bg': 'var(--av-danger-soft)',
        '--av-button-bg-hover': 'var(--av-danger-soft-hover)',
        '--av-button-bg-pressed': 'var(--av-danger-soft-hover)',
        '--av-button-fg': 'var(--av-danger-soft-foreground)',
      },
      cancel: 'Cancel',
      confirm: 'Delete Account',
      confirmVariant: 'danger',
    },
  ];
}

Placements

import { TitleCasePipe } from '@angular/common';
import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import type { AvAlertDialogPlacement } from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-placements-demo',
  imports: [
    TitleCasePipe,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex flex-wrap gap-4">
      @for (placement of placements; track placement) {
        <av-alert-dialog [placement]="placement">
          <button av-button variant="secondary" av-alert-dialog-trigger>
            {{ placement | titlecase }}
          </button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="accent"></div>
                <h2 av-alert-dialog-heading>
                  {{
                    placement === 'auto'
                      ? 'Auto Placement'
                      : (placement | titlecase) + ' Position'
                  }}
                </h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  @if (placement === 'auto') {
                    Automatically positions at the bottom on mobile and center on desktop for
                    optimal user experience.
                  } @else {
                    This dialog is positioned at the {{ placement }} of the viewport. Critical
                    confirmations are typically centered for maximum attention.
                  }
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      }
    </div>`,
})
export class AlertDialogPlacementsDemo {
  readonly placements: readonly AvAlertDialogPlacement[] = ['auto', 'top', 'center', 'bottom'];
}

Sizes

import { TitleCasePipe } from '@angular/common';
import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import type { AvAlertDialogSize } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-sizes-demo',
  imports: [
    TitleCasePipe,
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex flex-wrap gap-4">
      @for (size of sizes; track size) {
        <av-alert-dialog [size]="size">
          <button av-button variant="secondary" av-alert-dialog-trigger>
            {{ size | titlecase }}
          </button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog>
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="default" class="bg-default text-foreground">
                  <app-icon icon="solar:rocket-linear" size="20" />
                </div>
                <h2 av-alert-dialog-heading>Size: {{ size | titlecase }}</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  @if (size === 'cover') {
                    This alert dialog uses the <code>cover</code> size variant. It spans the full
                    screen with margins: 16px on mobile and 40px on desktop. Maintains rounded
                    corners and standard padding. Perfect for critical confirmations that need
                    maximum width while preserving alert dialog aesthetics.
                  } @else {
                    This alert dialog uses the <code>{{ size }}</code> size variant. On mobile
                    devices, all sizes adapt to near full-width for optimal viewing. On desktop,
                    each size provides a different maximum width to suit various content needs.
                  }
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      }
    </div>`,
})
export class AlertDialogSizesDemo {
  readonly sizes: readonly AvAlertDialogSize[] = ['xs', 'sm', 'md', 'lg', 'cover'];
}

Controlled State

With signal()

Control the alert dialog using Angular's signal for simple state management. Perfect for basic use cases.

Status: closed

With open / close / toggle helpers

Wrap a signal with helper methods like open(), close(), and toggle() for a cleaner imperative API.

Status: closed

import { Component, signal } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-controlled-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex max-w-md flex-col gap-8">
      <div class="flex flex-col gap-3">
        <h3 class="text-lg font-semibold text-foreground">With signal()</h3>
        <p class="text-sm leading-relaxed text-pretty text-muted">
          Control the alert dialog using Angular's <code class="text-foreground">signal</code> for
          simple state management. Perfect for basic use cases.
        </p>
        <div class="flex flex-col items-start gap-3 rounded-2xl bg-surface p-4 shadow-sm">
          <div class="flex w-full items-center justify-between">
            <p class="text-xs text-muted">
              Status:
              <span class="font-mono font-medium text-foreground">
                {{ signalOpen() ? 'open' : 'closed' }}
              </span>
            </p>
          </div>
          <div class="flex gap-2">
            <button av-button size="sm" variant="secondary" (click)="openSignal()">
              Open Dialog
            </button>
            <button av-button size="sm" variant="tertiary" (click)="toggleSignal()">
              Toggle
            </button>
          </div>
        </div>

        <av-alert-dialog [(open)]="signalOpen">
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="accent"></div>
                <h2 av-alert-dialog-heading>Controlled with signal()</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  This alert dialog is controlled by Angular's <code>signal</code>. Bind
                  <code>[(open)]</code> to manage the dialog state externally.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>

      <div class="flex flex-col gap-3">
        <h3 class="text-lg font-semibold text-foreground">With open / close / toggle helpers</h3>
        <p class="text-sm leading-relaxed text-pretty text-muted">
          Wrap a signal with helper methods like <code class="text-foreground">open()</code>,
          <code>close()</code>, and <code>toggle()</code> for a cleaner imperative API.
        </p>
        <div class="flex flex-col items-start gap-3 rounded-2xl bg-surface p-4 shadow-sm">
          <div class="flex w-full items-center justify-between">
            <p class="text-xs text-muted">
              Status:
              <span class="font-mono font-medium text-foreground">
                {{ overlayOpen() ? 'open' : 'closed' }}
              </span>
            </p>
          </div>
          <div class="flex gap-2">
            <button av-button size="sm" variant="secondary" (click)="open()">Open Dialog</button>
            <button av-button size="sm" variant="tertiary" (click)="toggle()">Toggle</button>
          </div>
        </div>

        <av-alert-dialog [(open)]="overlayOpen">
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="success"></div>
                <h2 av-alert-dialog-heading>Controlled with helpers</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  Dedicated methods for common operations — call <code>open()</code>,
                  <code>close()</code>, or <code>toggle()</code> without hand-writing signal
                  updates at every call site.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>
    </div>`,
})
export class AlertDialogControlledDemo {
  readonly signalOpen = signal(false);
  readonly overlayOpen = signal(false);

  openSignal(): void {
    this.signalOpen.set(true);
  }

  toggleSignal(): void {
    this.signalOpen.update((value) => !value);
  }

  open(): void {
    this.overlayOpen.set(true);
  }

  close(): void {
    this.overlayOpen.set(false);
  }

  toggle(): void {
    this.overlayOpen.update((value) => !value);
  }
}

Custom Icon

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-custom-icon-demo',
  imports: [
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog>
      <button av-button variant="secondary" av-alert-dialog-trigger>Reset Password</button>
      <ng-template avAlertDialogContent>
        <div av-alert-dialog-dialog class="sm:max-w-[400px]">
          <av-alert-dialog-close-trigger />
          <div av-alert-dialog-header>
            <div av-alert-dialog-icon status="warning">
              <app-icon icon="solar:lock-unlocked-linear" size="20" />
            </div>
            <h2 av-alert-dialog-heading>Reset your password?</h2>
          </div>
          <div av-alert-dialog-body>
            <p>
              We'll send a password reset link to your email address. You'll need to create a new
              password to regain access to your account.
            </p>
          </div>
          <div av-alert-dialog-footer>
            <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
            <button av-button av-alert-dialog-close>Send Reset Link</button>
          </div>
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogCustomIconDemo {}

Custom Trigger

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-custom-trigger-demo',
  imports: [
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog>
      <div
        av-alert-dialog-trigger
        class="group flex max-w-xs items-center gap-3 rounded-2xl bg-surface p-4 shadow-sm select-none hover:bg-surface-secondary"
      >
        <div
          class="flex size-12 shrink-0 items-center justify-center rounded-xl bg-danger-soft text-danger-soft-foreground"
        >
          <app-icon icon="solar:trash-bin-trash-linear" size="24" />
        </div>
        <div class="flex flex-1 flex-col gap-0.5">
          <p class="text-sm font-semibold">Delete Item</p>
          <p class="text-xs text-muted">Permanently remove this item</p>
        </div>
      </div>
      <ng-template avAlertDialogContent>
        <div av-alert-dialog-dialog class="sm:max-w-[400px]">
          <av-alert-dialog-close-trigger />
          <div av-alert-dialog-header>
            <div av-alert-dialog-icon status="danger">
              <app-icon icon="solar:trash-bin-trash-linear" size="20" />
            </div>
            <h2 av-alert-dialog-heading>Delete this item?</h2>
          </div>
          <div av-alert-dialog-body>
            <p>
              Use <code>av-alert-dialog-trigger</code> to create custom trigger elements beyond
              standard buttons. This example shows a card-style trigger with icons and descriptive
              text.
            </p>
          </div>
          <div av-alert-dialog-footer>
            <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
            <button av-button variant="danger" av-alert-dialog-close>Delete Item</button>
          </div>
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogCustomTriggerDemo {}

Backdrop Variants

import { TitleCasePipe } from '@angular/common';
import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import type { AvAlertDialogBackdropVariant } from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-backdrop-variants-demo',
  imports: [
    TitleCasePipe,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex flex-wrap gap-4">
      @for (variant of backdropVariants; track variant) {
        <av-alert-dialog [backdrop]="variant">
          <button av-button variant="secondary" av-alert-dialog-trigger>
            {{ variant | titlecase }}
          </button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="accent"></div>
                <h2 av-alert-dialog-heading>Backdrop: {{ variant | titlecase }}</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  @switch (variant) {
                    @case ('opaque') {
                      An opaque dark backdrop that completely obscures the background, providing
                      maximum focus on the dialog.
                    }
                    @case ('blur') {
                      A blurred backdrop that softly obscures the background while maintaining
                      visual context.
                    }
                    @default {
                      A transparent backdrop that keeps the background fully visible, useful for
                      less critical confirmations.
                    }
                  }
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      }
    </div>`,
})
export class AlertDialogBackdropVariantsDemo {
  readonly backdropVariants: readonly AvAlertDialogBackdropVariant[] = [
    'opaque',
    'blur',
    'transparent',
  ];
}

Custom Backdrop

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-custom-backdrop-demo',
  imports: [
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog
      backdrop="blur"
      backdrop-class="bg-linear-to-t from-red-950/90 via-red-950/50 to-transparent dark:from-red-950/95 dark:via-red-950/60"
    >
      <button av-button variant="danger" av-alert-dialog-trigger>Delete Account</button>
      <ng-template avAlertDialogContent>
        <div av-alert-dialog-dialog class="sm:max-w-[420px]">
          <av-alert-dialog-close-trigger />
          <div av-alert-dialog-header class="items-center text-center">
            <div av-alert-dialog-icon status="danger">
              <app-icon icon="solar:danger-triangle-linear" size="20" />
            </div>
            <h2 av-alert-dialog-heading>Permanently delete your account?</h2>
          </div>
          <div av-alert-dialog-body>
            <p>
              This action cannot be undone. All your data, settings, and content will be
              permanently removed from our servers. The dramatic red backdrop emphasizes the
              severity and irreversibility of this decision.
            </p>
          </div>
          <div av-alert-dialog-footer class="flex-col-reverse">
            <button av-button full-width av-alert-dialog-close>Keep Account</button>
            <button av-button full-width variant="danger" av-alert-dialog-close>
              Delete Forever
            </button>
          </div>
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogCustomBackdropDemo {}

Dismiss Behavior

Require explicit action (default)

Alert dialogs require explicit action by default. Backdrop click and Escape are disabled unless you opt in with dismissable and keyboard-dismiss-disabled="false".

Dismissable

For less critical confirmations, set dismissable and keyboard-dismiss-disabled="false" on av-alert-dialog.

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-dismiss-behavior-demo',
  imports: [
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex max-w-sm flex-col gap-6">
      <div class="flex flex-col gap-2">
        <h3 class="text-lg font-semibold">Require explicit action (default)</h3>
        <p class="text-sm text-muted">
          Alert dialogs require explicit action by default. Backdrop click and Escape are disabled
          unless you opt in with <code>dismissable</code> and
          <code>keyboard-dismiss-disabled="false"</code>.
        </p>
        <av-alert-dialog>
          <button av-button variant="secondary" av-alert-dialog-trigger>Open Alert Dialog</button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="danger">
                  <app-icon icon="solar:info-circle-linear" size="20" />
                </div>
                <h2 av-alert-dialog-heading>Action required</h2>
                <p class="text-sm leading-5 text-muted">
                  Clicking the backdrop or pressing Escape won't close this dialog
                </p>
              </div>
              <div av-alert-dialog-body>
                <p>
                  Try clicking outside this alert dialog on the overlay — it won't close. You must
                  use the action buttons to dismiss it.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>

      <div class="flex flex-col gap-2">
        <h3 class="text-lg font-semibold">Dismissable</h3>
        <p class="text-sm text-muted">
          For less critical confirmations, set <code>dismissable</code> and
          <code>keyboard-dismiss-disabled="false"</code> on <code>av-alert-dialog</code>.
        </p>
        <av-alert-dialog [dismissable]="true" [keyboard-dismiss-disabled]="false">
          <button av-button variant="secondary" av-alert-dialog-trigger>Open Alert Dialog</button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <av-alert-dialog-close-trigger />
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="accent">
                  <app-icon icon="solar:info-circle-linear" size="20" />
                </div>
                <h2 av-alert-dialog-heading>Dismissable</h2>
                <p class="text-sm leading-5 text-muted">Backdrop click and Escape work</p>
              </div>
              <div av-alert-dialog-body>
                <p>
                  Click outside or press Escape to dismiss. Use this pattern for low-stakes
                  confirmations that don't require forced attention.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>
    </div>`,
})
export class AlertDialogDismissBehaviorDemo {}

Close Methods

Using av-alert-dialog-close

The simplest way to close a dialog. Add av-alert-dialog-close to any button within the dialog. When clicked, it will automatically close the dialog.

Using imperative close

Drive visibility with [(open)] and call close() yourself. This gives you full control over when and how to close the dialog, allowing you to add custom logic before closing.

import { Component, signal } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-close-methods-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex max-w-2xl flex-col gap-8">
      <div class="flex flex-col gap-2">
        <h3 class="text-lg font-semibold">Using av-alert-dialog-close</h3>
        <p class="text-sm text-muted">
          The simplest way to close a dialog. Add <code>av-alert-dialog-close</code> to any button
          within the dialog. When clicked, it will automatically close the dialog.
        </p>
        <av-alert-dialog>
          <button av-button variant="secondary" av-alert-dialog-trigger>Open Dialog</button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="accent"></div>
                <h2 av-alert-dialog-heading>Using av-alert-dialog-close</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  Click either button below - both have <code>av-alert-dialog-close</code> and will
                  close the dialog automatically.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                <button av-button av-alert-dialog-close>Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>

      <div class="flex flex-col gap-2">
        <h3 class="text-lg font-semibold">Using imperative close</h3>
        <p class="text-sm text-muted">
          Drive visibility with <code>[(open)]</code> and call <code>close()</code> yourself. This
          gives you full control over when and how to close the dialog, allowing you to add custom
          logic before closing.
        </p>
        <av-alert-dialog [(open)]="imperativeOpen">
          <button av-button variant="secondary" av-alert-dialog-trigger>Open Dialog</button>
          <ng-template avAlertDialogContent>
            <div av-alert-dialog-dialog class="sm:max-w-[400px]">
              <div av-alert-dialog-header>
                <div av-alert-dialog-icon status="success"></div>
                <h2 av-alert-dialog-heading>Using imperative close</h2>
              </div>
              <div av-alert-dialog-body>
                <p>
                  The buttons below call <code>close()</code> on the host. You can add validation
                  or other logic before setting open to <code>false</code>.
                </p>
              </div>
              <div av-alert-dialog-footer>
                <button av-button variant="tertiary" (click)="close()">Cancel</button>
                <button av-button (click)="close()">Confirm</button>
              </div>
            </div>
          </ng-template>
        </av-alert-dialog>
      </div>
    </div>`,
})
export class AlertDialogCloseMethodsDemo {
  readonly imperativeOpen = signal(false);

  close(): void {
    this.imperativeOpen.set(false);
  }
}

Programmatic

Use AvAlertDialogService to open confirmation dialogs imperatively without declaring overlay markup in your template.

Open a confirmation dialog programmatically with AvAlertDialogService.confirm().

import { Component, inject, signal } from '@angular/core';
import { AvAlertDialogService, AvButtonComponent } from '@avesra/angular';
import type { AvAlertDialogCloseResult } from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-service-demo',
  imports: [AvButtonComponent],
  template: `<div class="flex max-w-md flex-col gap-4">
      <p class="text-sm text-muted">
        Open a confirmation dialog programmatically with
        <code>AvAlertDialogService.confirm()</code>.
      </p>
      <button av-button variant="danger" (click)="confirmDelete()">Delete Project</button>
      @if (lastResult() !== null) {
        <p class="text-sm text-muted">
          Last result:
          <code class="font-mono text-foreground">{{ lastResult() }}</code>
        </p>
      }
    </div>`,
})
export class AlertDialogServiceDemo {
  private readonly alertDialog = inject(AvAlertDialogService);

  readonly lastResult = signal<AvAlertDialogCloseResult | 'dismissed' | null>(null);

  confirmDelete(): void {
    this.alertDialog
      .confirm({
        title: 'Delete project permanently?',
        description:
          'This will permanently delete My Awesome Project and all of its data. This action cannot be undone.',
        confirmText: 'Delete Project',
        cancelText: 'Cancel',
        status: 'danger',
      })
      .afterClosed()
      .subscribe((result) => {
        this.lastResult.set(result ?? 'dismissed');
      });
  }
}

Custom Animations

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

const kinematicBackdrop = [
  'data-[entering]:duration-400',
  'data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]',
  'data-[exiting]:duration-200',
  'data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]',
].join(' ');

const kinematicContainer = [
  'data-[entering]:animate-in',
  'data-[entering]:fade-in-0',
  'data-[entering]:zoom-in-95',
  'data-[entering]:duration-400',
  'data-[entering]:ease-[cubic-bezier(0.16,1,0.3,1)]',
  'data-[exiting]:animate-out',
  'data-[exiting]:fade-out-0',
  'data-[exiting]:zoom-out-95',
  'data-[exiting]:duration-200',
  'data-[exiting]:ease-[cubic-bezier(0.7,0,0.84,0)]',
].join(' ');

const fluidBackdrop = [
  'data-[entering]:duration-500',
  'data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]',
  'data-[exiting]:duration-200',
  'data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]',
].join(' ');

const fluidContainer = [
  'data-[entering]:animate-in',
  'data-[entering]:fade-in-0',
  'data-[entering]:slide-in-from-bottom-4',
  'data-[entering]:duration-500',
  'data-[entering]:ease-[cubic-bezier(0.25,1,0.5,1)]',
  'data-[exiting]:animate-out',
  'data-[exiting]:fade-out-0',
  'data-[exiting]:slide-out-to-bottom-2',
  'data-[exiting]:duration-200',
  'data-[exiting]:ease-[cubic-bezier(0.5,0,0.75,0)]',
].join(' ');

@Component({
  selector: 'app-alert-dialog-custom-animations-demo',
  imports: [
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex flex-wrap gap-4">
      <av-alert-dialog
        [backdrop-class]="kinematicBackdrop"
        [container-class]="kinematicContainer"
      >
        <button av-button variant="secondary" av-alert-dialog-trigger>Kinematic Scale</button>
        <ng-template avAlertDialogContent>
          <div av-alert-dialog-dialog class="sm:max-w-[400px]">
            <av-alert-dialog-close-trigger />
            <div av-alert-dialog-header>
              <div av-alert-dialog-icon status="accent">
                <app-icon icon="solar:stars-linear" size="20" />
              </div>
              <h2 av-alert-dialog-heading>Kinematic Scale Animation</h2>
            </div>
            <div av-alert-dialog-body>
              <p class="mt-1">
                Physics-based elastic scaling. Simulates a high-damping spring system with fast
                transient response and prolonged settling time. Ideal for Alert Dialogs and Modals.
              </p>
            </div>
            <div av-alert-dialog-footer>
              <button av-button variant="tertiary" av-alert-dialog-close>Close</button>
              <button av-button av-alert-dialog-close>Try Again</button>
            </div>
          </div>
        </ng-template>
      </av-alert-dialog>

      <av-alert-dialog
        [backdrop-class]="fluidBackdrop"
        [container-class]="fluidContainer"
      >
        <button av-button variant="secondary" av-alert-dialog-trigger>Fluid Slide</button>
        <ng-template avAlertDialogContent>
          <div av-alert-dialog-dialog class="sm:max-w-[400px]">
            <av-alert-dialog-close-trigger />
            <div av-alert-dialog-header>
              <div av-alert-dialog-icon status="accent">
                <app-icon icon="solar:arrow-up-linear" size="20" />
              </div>
              <h2 av-alert-dialog-heading>Fluid Slide Animation</h2>
            </div>
            <div av-alert-dialog-body>
              <p class="mt-1">
                Simulates movement through a medium with fluid resistance. Eliminates mechanical
                linearity for a natural, grounded feel. Perfect for Bottom Sheets or Toasts.
              </p>
            </div>
            <div av-alert-dialog-footer>
              <button av-button variant="tertiary" av-alert-dialog-close>Close</button>
              <button av-button av-alert-dialog-close>Try Again</button>
            </div>
          </div>
        </ng-template>
      </av-alert-dialog>
    </div>`,
})
export class AlertDialogCustomAnimationsDemo {
  readonly kinematicBackdrop = kinematicBackdrop;
  readonly kinematicContainer = kinematicContainer;
  readonly fluidBackdrop = fluidBackdrop;
  readonly fluidContainer = fluidContainer;
}

Scroll Behavior

inside

Only the dialog body scrolls. Header and footer stay fixed while long content overflows inside the body.

outside

The entire dialog scrolls within the viewport when content is taller than the available space.

import { TitleCasePipe } from '@angular/common';
import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';
import type { AvAlertDialogScroll } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-alert-dialog-scroll-behavior-demo',
  imports: [
    TitleCasePipe,
    AppIconComponent,
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<div class="flex max-w-sm flex-col gap-6">
      @for (mode of scrollModes; track mode.value) {
        <div class="flex flex-col gap-2">
          <h3 class="text-lg font-semibold">{{ mode.value }}</h3>
          <p class="text-sm text-muted">{{ mode.description }}</p>
          <av-alert-dialog [scroll]="mode.value">
            <button av-button variant="secondary" av-alert-dialog-trigger>
              Open ({{ mode.value | titlecase }})
            </button>
            <ng-template avAlertDialogContent>
              <div av-alert-dialog-dialog class="sm:max-w-[400px]">
                <av-alert-dialog-close-trigger />
                <div av-alert-dialog-header>
                  <div av-alert-dialog-icon status="accent">
                    <app-icon icon="solar:info-circle-linear" size="20" />
                  </div>
                  <h2 av-alert-dialog-heading>
                    Scroll: {{ mode.value | titlecase }}
                  </h2>
                  <p class="text-sm leading-5 text-muted">{{ mode.hint }}</p>
                </div>
                <div av-alert-dialog-body>
                  @for (paragraph of scrollParagraphs; track paragraph) {
                    <p class="mb-3">
                      Paragraph {{ paragraph }}: Lorem ipsum dolor sit amet, consectetur adipiscing
                      elit. Nullam pulvinar risus non risus hendrerit venenatis. Pellentesque sit
                      amet hendrerit risus, sed porttitor quam.
                    </p>
                  }
                </div>
                <div av-alert-dialog-footer>
                  <button av-button variant="tertiary" av-alert-dialog-close>Cancel</button>
                  <button av-button av-alert-dialog-close>Confirm</button>
                </div>
              </div>
            </ng-template>
          </av-alert-dialog>
        </div>
      }
    </div>`,
})
export class AlertDialogScrollBehaviorDemo {
  readonly scrollModes: readonly {
    value: AvAlertDialogScroll;
    description: string;
    hint: string;
  }[] = [
    {
      value: 'inside',
      description:
        'Only the dialog body scrolls. Header and footer stay fixed while long content overflows inside the body.',
      hint: 'Content scrolls within the body; header and footer stay put.',
    },
    {
      value: 'outside',
      description:
        'The entire dialog scrolls within the viewport when content is taller than the available space.',
      hint: 'The whole dialog moves with the viewport scroll.',
    },
  ];

  readonly scrollParagraphs = Array.from({ length: 24 }, (_, index) => index + 1);
}
import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-stacked-footer-actions-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog backdrop="blur">
      <button av-button variant="secondary" av-alert-dialog-trigger>Premium offer</button>
      <ng-template avAlertDialogContent>
        <div av-alert-dialog-dialog class="sm:max-w-[360px]">
          <div av-alert-dialog-header class="items-center text-center">
            <div av-alert-dialog-icon status="accent">✨</div>
            <h2 av-alert-dialog-heading>Upgrade to Pro</h2>
          </div>
          <div av-alert-dialog-body class="text-center">
            <p>Unlock advanced components, themes, and priority support.</p>
          </div>
          <div av-alert-dialog-footer class="flex-col-reverse">
            <button av-button class="w-full" av-alert-dialog-close>Upgrade now</button>
            <button av-button class="w-full" variant="tertiary" av-alert-dialog-close>
              Maybe later
            </button>
          </div>
          <av-alert-dialog-close-trigger />
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogStackedFooterActionsDemo {}

Customization

Tailwind CSS

import { Component } from '@angular/core';
import {
  AvAlertDialogImports,
  AvButtonComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-alert-dialog-custom-styles-demo',
  imports: [
    AvAlertDialogImports,
    AvButtonComponent,
  ],
  template: `<av-alert-dialog backdrop="blur">
      <button av-button variant="secondary" av-alert-dialog-trigger>Sign out</button>
      <ng-template avAlertDialogContent>
        <div
          av-alert-dialog-dialog
          class="relative overflow-hidden border border-border/80 bg-surface shadow-2xl ring-1 ring-accent/10 sm:max-w-[400px] dark:border-border/90 dark:bg-surface dark:ring-accent/15"
        >
          <div
            aria-hidden="true"
            class="pointer-events-none absolute inset-x-0 top-0 h-24 bg-linear-to-b from-accent/6 to-transparent dark:from-accent/10"
          ></div>
          <div
            aria-hidden="true"
            class="pointer-events-none absolute inset-x-8 top-0 h-px bg-linear-to-r from-transparent via-accent/35 to-transparent dark:via-accent/45"
          ></div>
          <div av-alert-dialog-header class="relative">
            <div av-alert-dialog-icon status="accent"></div>
            <h2 av-alert-dialog-heading>Sign out of your account?</h2>
          </div>
          <div av-alert-dialog-body class="relative">
            <p class="text-muted">
              You will be signed out on this device. Unsaved work in
              <strong class="text-foreground">Acme Workspace</strong> may be lost unless it was
              saved to the cloud.
            </p>
          </div>
          <div av-alert-dialog-footer>
            <button av-button variant="tertiary" av-alert-dialog-close>Stay signed in</button>
            <button av-button av-alert-dialog-close>Sign out</button>
          </div>
        </div>
      </ng-template>
    </av-alert-dialog>`,
})
export class AlertDialogCustomStylesDemo {}

Global CSS

To customize the Alert Dialog component classes, you can use the @layer components directive. Learn more .

@layer components {
  .av-alert-dialog__backdrop {
    @apply bg-linear-to-br from-black/60 to-black/80;
  }

  .av-alert-dialog__dialog {
    @apply rounded-2xl border border-danger/20 shadow-2xl;
  }

  .av-alert-dialog__header {
    @apply gap-4;
  }

  .av-alert-dialog__icon {
    @apply size-16;
  }

  .av-alert-dialog__close-trigger {
    @apply rounded-full bg-white/10 hover:bg-white/20;
  }
}

Styling Reference

Avesra follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The Alert Dialog component uses these CSS classes:

Base Classes

  • .av-alert-dialog__trigger — Trigger element that opens the alert dialog
  • .av-alert-dialog__backdrop — Overlay backdrop behind the dialog
  • .av-alert-dialog__container — Positioning wrapper with placement support
  • .av-alert-dialog__dialog — Dialog content container
  • .av-alert-dialog__header — Header section for icon and title
  • .av-alert-dialog__heading — Heading text styles
  • .av-alert-dialog__body — Main content area
  • .av-alert-dialog__footer — Footer section for actions
  • .av-alert-dialog__icon — Icon container with status colors
  • .av-alert-dialog__close-trigger — Close button element

Backdrop Variants

  • .av-alert-dialog__backdrop--opaque — Opaque colored backdrop (default)
  • .av-alert-dialog__backdrop--blur — Blurred backdrop with glass effect
  • .av-alert-dialog__backdrop--transparent — Transparent backdrop (no overlay)

Status Variants (Icon)

  • .av-alert-dialog__icon--default — Default gray status
  • .av-alert-dialog__icon--accent — Accent blue status
  • .av-alert-dialog__icon--success — Success green status
  • .av-alert-dialog__icon--warning — Warning orange status
  • .av-alert-dialog__icon--danger — Danger red status

Interactive States

The component supports these interactive states:

  • Focus::focus-visible or [data-focus-visible="true"] — Applied to trigger, dialog, and close button
  • Hover::hover or [data-hovered="true"] — Applied to close button on hover
  • Active::active or [data-pressed="true"] — Applied to close button when pressed
  • Entering:[data-entering] — Applied during dialog opening animation
  • Exiting:[data-exiting] — Applied during dialog closing animation
  • Placement:[data-placement="*"] — Applied based on dialog position (auto, top, center, bottom)

Accessibility

Implements WAI-ARIA AlertDialog pattern :

  • Focus trap: Focus locked within alert dialog
  • Keyboard:ESC closes (when enabled), Tab cycles elements
  • Screen readers: Proper ARIA attributes with role="alertdialog"
  • Scroll lock: Body scroll disabled when open
  • Required action: Defaults to requiring explicit user action (no backdrop/ESC dismiss)

API Reference

See also Modal for general-purpose overlays.

PropTypeDefaultDescription
openbooleanfalseControls whether the alert dialog is open. Supports two-way binding with [(open)] (av-alert-dialog).
dismissablebooleanfalseWhether clicking the backdrop closes the alert dialog (av-alert-dialog).
keyboard-dismiss-disabledbooleantrueDisables closing via the Escape key (av-alert-dialog).
backdrop'opaque' | 'blur' | 'transparent''opaque'Backdrop visual variant (av-alert-dialog).
backdrop-classstring''Extra CSS classes merged onto the visual backdrop after the variant BEM classes (av-alert-dialog).
container-classstring''Extra CSS classes merged onto the overlay container after the BEM classes — useful for custom enter/exit motion (av-alert-dialog).
placement'auto' | 'top' | 'center' | 'bottom''auto'Alert dialog position on screen (av-alert-dialog).
scroll'inside' | 'outside''inside'Scroll behavior for long content (av-alert-dialog).
size'xs' | 'sm' | 'md' | 'lg' | 'cover''md'Maximum width / layout preset (av-alert-dialog).
scroll'inside' | 'outside' | undefined—Scroll behavior. Inherits from av-alert-dialog when omitted (div[av-alert-dialog-dialog]).
size'xs' | 'sm' | 'md' | 'lg' | 'cover' | undefined—Size preset. Inherits from av-alert-dialog when omitted (div[av-alert-dialog-dialog]).
aria-labelstring—Accessible label when the trigger has no visible text ([av-alert-dialog-trigger]).

Made with ❤ by SyntaxHertz. Open source and available on GitHub.