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.
| Prop | Type | Default | Description |
|---|---|---|---|
key | string | undefined | — | Routes messages with a matching key to this region (av-toast). |
life | number | 4000 | Default 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-toasts | number | 3 | Maximum number of visible stacked toasts (av-toast). |
width | number | 460 | Toast region width in pixels (av-toast). |
gap | number | 12 | Gap between stacked toasts in pixels (av-toast). |
scale-factor | number | 0.05 | Scale factor applied per stacked toast when collapsed (av-toast). |
expand | boolean | false | Keeps the stack expanded. When false, hover expands peeks into a full list (av-toast). |
swipe-threshold | number | 50 | Vertical swipe distance in pixels required to dismiss a toast (av-toast). |
prevent-duplicates | boolean | false | Prevents adding another open toast with the same title and description (av-toast). |
closed | EventEmitter<AvToastItemCloseEvent> | — | Emits after a toast is removed (av-toast). |
AvToastService
Inject AvToastService to publish and dismiss toasts from anywhere in the app.
| Prop | Type | Default | Description |
|---|---|---|---|
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).
| Prop | Type | Default | Description |
|---|---|---|---|
description | string | — | Optional description text under the title. |
variant | 'default' | 'accent' | 'success' | 'warning' | 'danger' | 'default' | Visual variant of the toast. |
key | string | — | Routes the toast to a matching `av-toast` region key. |
life | number | 4000 | Auto-dismiss timeout in milliseconds. Set to `0` (or use `sticky: true`) for persistent toasts. |
sticky | boolean | false | Keeps the toast until dismissed. |
closable | boolean | true | Shows the close button when true. |
isLoading | boolean | false | Shows a loading spinner instead of the variant indicator. |
hideIndicator | boolean | false | Hides the default variant indicator. |
actionLabel | string | — | Label for the optional action button. |
action | () => void | — | Callback when the action button is pressed. |
onClose | () => void | — | Callback when the toast is closed. |