AvesraAvesrabeta

Toast

Display temporary notifications and messages with automatic dismissal and customizable placement.

Import

import {
  AvToastComponent,
  AvToastService,
} from '@avesra/angular';

Usage

import { Component, inject } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService } from '@avesra/angular';

@Component({
  selector: 'app-toast-usage-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `
<button av-button variant="secondary" (click)="showToast()">Show toast</button>
<av-toast placement="bottom" />
`,
})
export class ToastUsageDemo {
  private readonly toast = inject(AvToastService);

  showToast(): void {
    this.toast.add('Event has been created');
  }
}

Anatomy

Toast is service-driven. Call provideAvesraToast() (or provide AvToastService), mount a av-toast region, then inject the service anywhere to publish messages. Hover the stack to expand peeks into a full list.

import { provideAvesraToast, AvToastService } from '@avesra/angular';

// app.config.ts
providers: [provideAvesraToast()]

// Root template — toast region (optional icon slots)
<av-toast placement="bottom">
  <!-- <ng-template #successIcon>…</ng-template> -->
</av-toast>

// Anywhere — inject and publish
private readonly toast = inject(AvToastService);

this.toast.add('Title', {
  description: 'Optional description',
  variant: 'success',
  actionLabel: 'Undo',
  action: () => undefined,
});

this.toast.promise(save(), {
  loading: 'Saving…',
  success: 'Saved',
  error: 'Failed',
});

Variants

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-basic-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      <button av-button size="sm" variant="tertiary" class="text-muted" (click)="showDefaultToast()">
        Default toast
      </button>
      <button av-button size="sm" variant="secondary" (click)="showAccentToast()">
        Accent toast
      </button>
      <button av-button size="sm" variant="tertiary" class="text-success" (click)="showSuccessToast()">
        Success toast
      </button>
      <button av-button size="sm" variant="tertiary" class="text-warning" (click)="showWarningToast()">
        Warning toast
      </button>
      <button av-button size="sm" variant="danger-soft" (click)="showDangerToast()">
        Danger toast
      </button>
    </div>`,
})
export class ToastBasicDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Placements

Set placement on av-toast — bottom, bottom-start, bottom-end, top, top-start, or top-end. Mount separate regions for multiple placements.

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-placements-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      @for (placement of placements; track placement) {
        <button av-button size="sm" variant="secondary" (click)="showPlacement(placement)">
          {{ placement }}
        </button>
      }
    </div>`,
})
export class ToastPlacementsDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Simple Toasts

Use shorthand helpers — success(), info(), warning(), and danger() — for title-only toasts.

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-simple-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      <button av-button size="sm" variant="secondary" (click)="showSimpleDefault()">Default</button>
      <button av-button size="sm" variant="secondary" (click)="showSimpleSuccess()">Success</button>
      <button av-button size="sm" variant="secondary" (click)="showSimpleInfo()">Info</button>
      <button av-button size="sm" variant="secondary" (click)="showSimpleWarning()">Warning</button>
      <button av-button size="sm" variant="secondary" (click)="showSimpleDanger()">Error</button>
    </div>`,
})
export class ToastSimpleDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Custom Indicators

Pass hideIndicator: true to omit the default variant icon. Enable prevent-duplicates on the region to suppress repeated title and description pairs while that toast is still open.

import { Component, inject } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService } from '@avesra/angular';

@Component({
  selector: 'app-toast-other-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `
<div class="flex flex-wrap gap-3">
  <button av-button size="sm" variant="secondary" (click)="showWithoutIndicator()">
    Without indicator
  </button>
  <button av-button size="sm" variant="secondary" (click)="showDuplicateToast()">
    Prevent duplicates
  </button>
</div>
<av-toast placement="bottom" prevent-duplicates />
`,
})
export class ToastOtherDemo {
  private readonly toast = inject(AvToastService);

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
    });
  }
}

Promise & Loading

toast.promise() shows a loading toast, then updates the same toast to success or danger when the promise settles. success / error accept a string or a callback.

import { Component, inject } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService } from '@avesra/angular';

@Component({
  selector: 'app-toast-promise-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `
<div class="flex flex-wrap gap-3">
  <button av-button size="sm" variant="secondary" (click)="saveSuccess()">
    Save (success)
  </button>
  <button av-button size="sm" variant="secondary" (click)="saveError()">
    Save (error)
  </button>
</div>
<av-toast placement="bottom" />
`,
})
export class ToastPromiseDemo {
  private readonly toast = inject(AvToastService);

  saveSuccess(): void {
    this.toast.promise(this.fakeSave(true), {
      loading: 'Saving changes…',
      success: (name) => `Saved ${name}`,
      error: 'Could not save',
    });
  }

  saveError(): void {
    this.toast.promise(this.fakeSave(false), {
      loading: 'Saving changes…',
      success: 'Saved',
      error: (err) => (err instanceof Error ? err.message : 'Could not save'),
    });
  }

  private fakeSave(ok: boolean): Promise<string> {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (ok) {
          resolve('draft.md');
        } else {
          reject(new Error('Network unavailable'));
        }
      }, 1500);
    });
  }
}

Loading

Pass isLoading: true with life: 0 (or sticky: true) for a persistent loading toast, then close() the id and show a result toast — or use promise() above for the common async flow.

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-loading-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      <button av-button size="sm" variant="secondary" (click)="showUploadLoading()">
        Upload with loading
      </button>
      <button av-button size="sm" variant="secondary" (click)="showPaymentLoading()">
        Payment processing
      </button>
      <button av-button size="sm" variant="secondary" (click)="showLoadingToError()">
        Loading to error
      </button>
    </div>`,
})
export class ToastLoadingDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Hover Expand

Collapsed stacks peek behind the front toast. Hover the region to expand full heights and pause auto-dismiss. Pass expand to keep the stack open. Swipe vertically to dismiss on touch devices.

Add a few toasts, then hover the stack to expand peeks into a full list.

import { Component, inject } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService } from '@avesra/angular';

@Component({
  selector: 'app-toast-hover-expand-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `
<div class="flex flex-col gap-3">
  <p class="text-sm text-muted">
    Add a few toasts, then hover the stack to expand peeks into a full list.
  </p>
  <div class="flex flex-wrap gap-3">
    <button av-button size="sm" variant="secondary" (click)="addStack()">
      Add 3 stacked toasts
    </button>
    <button av-button size="sm" variant="tertiary" (click)="toast.clear()">
      Clear
    </button>
  </div>
</div>
<av-toast placement="bottom" [max-visible-toasts]="4" />
`,
})
export class ToastHoverExpandDemo {
  readonly toast = inject(AvToastService);

  addStack(): void {
    this.toast.success('File uploaded');
    this.toast.info('Syncing changes…');
    this.toast.warning('Storage almost full');
  }
}

Callbacks

Control auto-dismiss with life (milliseconds). Set life: 0 or sticky: true for persistent toasts. Use onClose when a toast is removed, and clear() to dismiss all toasts in a region.

Closed history

No toasts closed yet. Try closing one above!

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-timeouts-amp-callbacks-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      <button av-button size="sm" variant="secondary" (click)="showShortTimeout()">
        Custom timeout (3s)
      </button>
      <button av-button size="sm" variant="secondary" (click)="showLongTimeout()">
        Custom timeout (10s)
      </button>
      <button av-button size="sm" variant="secondary" (click)="showWithOnClose()">
        With onClose callback
      </button>
      <button av-button size="sm" variant="secondary" (click)="showPersistent()">
        Persistent toast
      </button>
      <button av-button size="sm" variant="outline" (click)="clearAll()">Clear all</button>
    </div>

    <div class="mt-4 space-y-2">
      <div class="flex items-center justify-between">
        <h4 class="text-sm font-medium text-foreground">Closed history</h4>
        @if (closedHistory().length > 0) {
          <button av-button size="sm" variant="tertiary" class="h-6 text-xs" (click)="clearHistory()">
            Clear
          </button>
        }
      </div>
      <div class="min-h-[120px] space-y-2 rounded-lg border border-border bg-default p-4">
        @if (closedHistory().length === 0) {
          <p class="text-sm text-muted">No toasts closed yet. Try closing one above!</p>
        } @else {
          @for (item of closedHistory(); track item.time + item.message) {
            <div
              class="flex items-start justify-between gap-3 rounded-md border border-border bg-surface px-3 py-2 text-sm"
            >
              <div class="flex-1">
                <span class="font-medium text-foreground">{{ item.message }}</span>
                <span class="ml-2 text-xs text-muted">({{ item.time }})</span>
              </div>
              <div
                class="flex size-5 shrink-0 items-center justify-center rounded-full bg-success/10 text-success"
              >
                <svg
                  class="size-3"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  viewBox="0 0 24 24"
                  aria-hidden="true"
                >
                  <path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
              </div>
            </div>
          }
        }
      </div>
    </div>`,
})
export class ToastTimeoutsAmpCallbacksDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Custom Queues

Route messages with the key option on each toast and a matching key on av-toast. Tune stacking per region with max-visible-toasts.

import { Component, inject, signal } from '@angular/core';
import { AvButtonComponent, AvToastComponent, AvToastService, type AvToastPlacement } from '@avesra/angular';

interface ClosedHistoryEntry {
  message: string;
  time: string;
}

@Component({
  selector: 'app-toast-custom-queues-demo',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService],
  template: `<div class="flex flex-wrap gap-3">
      <button av-button size="sm" variant="secondary" (click)="showNotificationQueue()">
        Add notification (max 2)
      </button>
      <button av-button size="sm" variant="danger-soft" (click)="showErrorQueue()">
        Add error (max 3)
      </button>
      <button
        av-button
        size="sm"
        variant="secondary"
        class="text-success"
        (click)="showSuccessQueue()"
      >
        Add success (max 1)
      </button>
    </div>`,
})
export class ToastCustomQueuesDemo {
private readonly toast = inject(AvToastService);

  readonly placements: AvToastPlacement[] = [
    'bottom',
    'bottom-start',
    'bottom-end',
    'top',
    'top-start',
    'top-end',
  ];

  readonly closedHistory = signal<ClosedHistoryEntry[]>([]);

  // —— Default ——

  showDefaultToast(): void {
    this.toast.add('You have been invited to join a team', {
      description: 'Bob sent you an invitation to join the Avesra team',
      actionLabel: 'Dismiss',
      action: () => this.toast.clear(),
    });
  }

  showAccentToast(): void {
    this.toast.info('You have 2 credits left', {
      description: 'Get a paid plan for more credits',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showSuccessToast(): void {
    this.toast.success('You have upgraded your plan', {
      description: 'You can continue using Avesra Chat',
      actionLabel: 'Billing',
      action: () => undefined,
    });
  }

  showWarningToast(): void {
    this.toast.warning('You have no credits left', {
      description: 'Upgrade to a paid plan to continue',
      actionLabel: 'Upgrade',
      action: () => undefined,
    });
  }

  showDangerToast(): void {
    this.toast.danger('Storage is full', {
      description:
        'Remove files to release space. Adding more text to demonstrate longer content display.',
      actionLabel: 'Remove',
      action: () => undefined,
    });
  }

  // —— Simple ——

  showSimpleDefault(): void {
    this.toast.add('Simple message');
  }

  showSimpleSuccess(): void {
    this.toast.success('Operation completed');
  }

  showSimpleInfo(): void {
    this.toast.info('New update available');
  }

  showSimpleWarning(): void {
    this.toast.warning('Please check your settings');
  }

  showSimpleDanger(): void {
    this.toast.danger('Something went wrong');
  }

  // —— Placements ——

  showPlacement(placement: AvToastPlacement): void {
    this.toast.add('Event created', {
      description: 'Event has been created',
      key: placement,
    });
  }

  // —— Loading ——

  showUploadLoading(): void {
    const id = this.toast.add('Uploading file...', {
      description: 'Please wait while we upload your file',
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('File uploaded', {
        description: 'Your file has been uploaded successfully',
      });
    }, 3000);
  }

  showPaymentLoading(): void {
    const id = this.toast.add('Processing payment...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.success('Payment processed', {
        description: 'Your payment has been processed successfully',
      });
    }, 2500);
  }

  showLoadingToError(): void {
    const id = this.toast.add('Saving changes...', {
      isLoading: true,
      life: 0,
      sticky: true,
      closable: false,
    });

    setTimeout(() => {
      this.toast.close(id);
      this.toast.danger('Failed to save', {
        description: 'Please try again',
      });
    }, 2000);
  }

  // —— Timeouts & callbacks ——

  showShortTimeout(): void {
    this.toast.add('File saved', {
      life: 3000,
      onClose: () => this.addToHistory('File saved (closed after 3 seconds)'),
    });
  }

  showLongTimeout(): void {
    this.toast.add('Changes saved', {
      life: 10000,
      onClose: () => this.addToHistory('Changes saved (closed after 10 seconds)'),
    });
  }

  showWithOnClose(): void {
    this.toast.success('Event created', {
      onClose: () => this.addToHistory('Event created (closed after default timeout)'),
    });
  }

  showPersistent(): void {
    this.toast.add('Important notification', {
      description: 'This toast will stay until dismissed',
      life: 0,
      onClose: () => this.addToHistory('Important notification (manually closed)'),
    });
  }

  clearHistory(): void {
    this.closedHistory.set([]);
  }

  // —— Custom queues ——

  showNotificationQueue(): void {
    this.toast.add('New notification', {
      description: 'You have a new message',
      key: 'queue-notifications',
    });
  }

  showErrorQueue(): void {
    this.toast.danger('Error occurred', {
      description: 'Failed to save changes',
      key: 'queue-errors',
    });
  }

  showSuccessQueue(): void {
    this.toast.success('Success!', {
      description: `Operation ${Date.now()}`,
      key: 'queue-success',
    });
  }

  // —— Other ——

  showWithoutIndicator(): void {
    this.toast.add('No indicator toast', {
      description: 'The default icon is hidden with hideIndicator.',
      hideIndicator: true,
    });
  }

  showDuplicateToast(): void {
    this.toast.add('Duplicate check', {
      description: 'Try clicking again — only one copy is shown.',
      key: 'dedupe',
    });
  }

  clearAll(): void {
    this.toast.clear();
  }

  private addToHistory(message: string): void {
    const time = new Date().toLocaleTimeString();

    this.closedHistory.update((prev) => [{ message, time }, ...prev].slice(0, 5));
  }
}

Setup

Register provideAvesraToast() (or AvToastService) and render a toast region in the root of your app. Hovering the region expands the stack.

import { Component, inject } from '@angular/core';
import {
  provideAvesraToast,
  AvButtonComponent,
  AvToastComponent,
  AvToastService,
} from '@avesra/angular';

// app.config.ts
// providers: [provideAvesraToast()]

@Component({
  selector: 'app-root',
  imports: [AvButtonComponent, AvToastComponent],
  providers: [AvToastService], // or provideAvesraToast() in app.config
  template: `
    <button av-button (click)="show()">Show toast</button>
    <av-toast placement="bottom" />
  `,
})
export class App {
  private readonly toast = inject(AvToastService);

  show(): void {
    this.toast.add('Simple message');
  }
}

Customization

Global CSS

To customize Toast classes, use the @layer components directive.

@layer components {
  .av-toast {
    @apply rounded-xl shadow-lg;
  }

  .av-toast__content {
    @apply gap-2;
  }
}

Styling Reference

Avesra follows the BEM methodology so variants and states stay reusable and easy to customize.

CSS Classes

Base Classes

  • .av-toast-region — Toast region container
  • .av-toast — Base toast container
  • .av-toast__content — Content wrapper for title and description
  • .av-toast__indicator — Icon / loading indicator container
  • .av-toast__title — Toast title text
  • .av-toast__description — Toast description text
  • .av-toast__action — Action button container
  • .av-toast__close-button — Close button

Variant Classes

  • .av-toast--default — Default gray variant
  • .av-toast--accent — Accent variant
  • .av-toast--success — Success variant
  • .av-toast--warning — Warning variant
  • .av-toast--danger — Danger variant

Interactive States

  • Front: [data-front] / [data-frontmost] — topmost visible toast
  • Expanded: [data-expanded] — hover / expand stack state
  • Index: [data-index] — position in the stack
  • Placement: region modifiers such as .av-toast-region--bottom
  • Hidden / Entering / Exiting: [data-hidden], [data-entering], [data-exiting]
  • Swipe: [data-swiping], [data-swipe-out]

API Reference

av-toast (region)

Region host that listens to AvToastService and renders stacked toasts.

PropTypeDefaultDescription
keystring | undefined—Routes messages with a matching key to this region (av-toast).
lifenumber4000Default auto-dismiss duration in milliseconds (av-toast).
placement'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end''bottom'Region placement on screen (av-toast).
max-visible-toastsnumber3Maximum number of visible stacked toasts (av-toast).
widthnumber460Toast region width in pixels (av-toast).
gapnumber12Gap between stacked toasts in pixels (av-toast).
scale-factornumber0.05Scale factor applied per stacked toast when collapsed (av-toast).
expandbooleanfalseKeeps the stack expanded. When false, hover expands peeks into a full list (av-toast).
swipe-thresholdnumber50Vertical swipe distance in pixels required to dismiss a toast (av-toast).
prevent-duplicatesbooleanfalsePrevents adding another open toast with the same title and description (av-toast).
closedEventEmitter<AvToastItemCloseEvent>—Emits after a toast is removed (av-toast).

AvToastService

Inject AvToastService to publish and dismiss toasts from anywhere in the app.

PropTypeDefaultDescription
add(title: string, options?: AvToastAddOptions) => string—Publishes a toast and returns its id. Options: description, variant, key, life, sticky, closable, isLoading, hideIndicator, actionLabel, action, onClose (AvToastService).
addAll(messages: Array<{ title: string } & AvToastAddOptions>) => void—Publishes multiple toasts in one batch (AvToastService).
update(id: string, patch: Partial<AvToastMessage>) => void—Patches an existing toast in place and resets its auto-dismiss timer (AvToastService).
promise(promise: Promise<T> | (() => Promise<T>), options: AvToastPromiseOptions<T>) => string—Shows a loading toast, then updates it to success or danger when the promise settles (AvToastService).
clear(key?: string) => void—Dismisses all toasts. When key is set, only matching regions clear (AvToastService).
close(id: string) => void—Dismisses a single toast by id (AvToastService).
success(title: string, options?: Omit<AvToastAddOptions, "variant">) => string—Publishes a success-variant toast and returns its id (AvToastService).
danger(title: string, options?: Omit<AvToastAddOptions, "variant">) => string—Publishes a danger-variant toast and returns its id (AvToastService).
info(title: string, options?: Omit<AvToastAddOptions, "variant">) => string—Publishes an accent-variant toast and returns its id (AvToastService).
warning(title: string, options?: Omit<AvToastAddOptions, "variant">) => string—Publishes a warning-variant toast and returns its id (AvToastService).
private readonly toast = inject(AvToastService);

// Basic toast (auto-dismisses after 4 seconds by default)
this.toast.add('Event has been created');

// Variant helpers (also auto-dismiss after 4 seconds by default)
this.toast.success('File saved');
this.toast.info('New update available');
this.toast.warning('Please check your settings');
this.toast.danger('Something went wrong');

// With options
this.toast.add('Event has been created', {
  description: 'Your event has been scheduled for tomorrow',
  variant: 'default',
  life: 5000,
  onClose: () => console.log('Closed'),
  actionLabel: 'View',
  action: () => undefined,
});

// Manual loading state (persistent toast — no auto-dismiss)
const loadingId = this.toast.add('Creating event...', {
  isLoading: true,
  life: 0,
  sticky: true,
  closable: false,
});

// Later, close and show result
this.toast.close(loadingId);
this.toast.success('Event created');

// Or use promise() for the common async flow
this.toast.promise(save(), {
  loading: 'Saving…',
  success: (name) => `Saved ${name}`,
  error: 'Could not save',
});

// Queue helpers
this.toast.close(id);
this.toast.clear();
this.toast.clear('queue-errors');

Toast options

Options accepted by add() and the variant helpers (success / info / warning / danger).

PropTypeDefaultDescription
descriptionstring—Optional description text under the title.
variant'default' | 'accent' | 'success' | 'warning' | 'danger''default'Visual variant of the toast.
keystring—Routes the toast to a matching `av-toast` region key.
lifenumber4000Auto-dismiss timeout in milliseconds. Set to `0` (or use `sticky: true`) for persistent toasts.
stickybooleanfalseKeeps the toast until dismissed.
closablebooleantrueShows the close button when true.
isLoadingbooleanfalseShows a loading spinner instead of the variant indicator.
hideIndicatorbooleanfalseHides the default variant indicator.
actionLabelstring—Label for the optional action button.
action() => void—Callback when the action button is pressed.
onClose() => void—Callback when the toast is closed.

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