Toast
OgeToastService shows stacked, auto-dismissing notifications in body-appended regions above every other surface — including open modals. Toasts never steal focus and never join the Escape stack; screen readers hear them through the document’s shared live regions (errors assert, the rest stay polite). Timers pause on hover, focus and while the tab is hidden, always resuming with the remaining time.
Severities
One sugar method per severity — the accent bar, icon and screen-reader mode follow. title adds a bold first line; announce overrides the politeness (errors assert by default).
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeToastService } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Save" (clicked)="save()" />
`,
})
export class Demo {
private readonly toasts = inject(OgeToastService);
protected save(): void {
this.toasts.success('Saved');
this.toasts.warning('Quota at 90%', { title: 'Heads up' });
this.toasts.error('Save failed'); // announces assertively
this.toasts.show({ message: 'Plain info toast' });
}
}Positions & stacking
Six logical positions (top/bottom × start/center/end) — RTL flips start/end automatically. The newest toast lands nearest the screen edge; extras beyond toastMaxVisible wait in a lossless FIFO queue and promote as slots free up. Try the burst button.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeToastService, provideOgeOverlayConfig } from '@oge-ui/overlay';
import type { ApplicationConfig } from '@angular/core';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Top center" (clicked)="notify()" />
`,
})
export class Demo {
private readonly toasts = inject(OgeToastService);
// 6 logical positions (RTL-aware); the default comes from config
protected notify(): void {
this.toasts.info('Top center', { position: 'top-center' });
}
}
// extras beyond toastMaxVisible queue FIFO and promote as slots free up
export const appConfig: ApplicationConfig = {
providers: [
provideOgeOverlayConfig({ toastPosition: 'bottom-end', toastMaxVisible: 5 }),
],
};Sticky, action & undo
sticky disables auto-dismiss — recommended whenever there's an action, so keyboard users can reach it. The action press closes with reason 'action'; awaiting ref.closed gives a clean undo pattern without extra state.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeToastService } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Delete row" severity="danger" (clicked)="deleteRow()" />
`,
})
export class Demo {
private readonly toasts = inject(OgeToastService);
protected async deleteRow(): Promise<void> {
const ref = this.toasts.show({
message: 'Row deleted',
sticky: true, // action toasts should stick
action: { text: 'Undo', handler: () => this.restore() },
});
const { reason } = await ref.closed; // 'action' | 'closeButton' | …
console.log('closed because of', reason);
}
private restore(): void {
console.log('restored');
}
}Promise toasts
promise() shows a sticky spinner toast and morphs it in place when the promise settles — success or error accepts a message or a function returning a message or a full patch. The auto-dismiss timer only starts on settle. Under the hood it's the public ref.update() — usable for any live-updating toast.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeToastService } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Publish" (clicked)="publish()" />
`,
})
export class Demo {
private readonly toasts = inject(OgeToastService);
// spinner → severity morph in place; the timer starts on settle
protected publish(): void {
this.toasts.promise(this.publishPages(), {
loading: 'Publishing…',
success: (result) => `Published ${result.count} pages`,
error: (error) => ({ title: 'Publish failed', message: String(error) }),
});
}
private publishPages(): Promise<{ count: number }> {
return fetch('/api/publish').then((response) => response.json());
}
}Coalescing & progress
With coalesce an identical toast doesn't pile up — the existing one gains a live ×N badge, restarts its timer and re-announces. progressBar shows the remaining time as a compositor-only bar that freezes exactly in sync with the paused timer.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeToastService } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Import" (clicked)="report()" />
`,
})
export class Demo {
private readonly toasts = inject(OgeToastService);
private readonly failedRows = [3, 17, 42];
protected report(): void {
// identical toasts merge into one with a live ×N badge
for (const _row of this.failedRows) {
this.toasts.error('Import row failed', { coalesce: true });
}
// remaining-time progress bar; pauses with the timer on hover/focus
this.toasts.info('With progress', { progressBar: true, displayTime: 6000 });
}
}Live announcements without a toast
Toasts, the grid, the tree list, the Gantt and the uploader all speak through OgeLiveAnnouncer — one polite and one assertive visually hidden region per document, created on first use. Inject it for your own status messages: identical messages within a second are dropped, a newer one inside the write delay wins, and the region clears after a few seconds so a repeat is heard again. A no-op on the server.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { OgeButton } from '@oge-ui/buttons';
import { OgeLiveAnnouncer } from '@oge-ui/overlay';
@Component({
selector: 'demo-root',
imports: [OgeButton],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-button text="Archive 3 items" (clicked)="archive()" />
`,
})
export class Demo {
private readonly announcer = inject(OgeLiveAnnouncer);
// no toast, no markup: the document's one shared polite region speaks
protected archive(): void {
this.announcer.announce('3 items archived');
// errors interrupt: the assertive region
// this.announcer.announce('Archive failed', 'assertive');
}
}Notes
- Defaults live in
provideOgeOverlayConfig({ toastPosition, toastDisplayTime, toastMaxVisible, toastProgressBar, toastCoalesceDuplicates }); the ✕ label, region label and count badge localize via themessagesblock. - Toast regions sit at the end of
<body>, so their buttons are the last Tab stops; focusing a toast pauses its timer (WCAG 2.2.1). templatereplaces the body for fully custom content — heavy interactive flows belong inOgeModalServiceinstead.