@onedevio/og-toast
v1.0.0
Published
Accessible, signal-based toast notifications for Angular 20.
Downloads
78
Maintainers
Readme
og-toast
Accessible, signal-based toast notifications for Angular 20.
- Standalone — one component, one service, no
NgModulerequired. - Signal state — works in zone-based and zoneless applications.
- Multi-position stacks — every toast picks its own corner; stacks are grouped automatically.
- Rich content — title/message, inline action buttons, your own template, or your own component.
- Async aware —
loadingtoasts,update(), andpromise()for one-line async feedback. - Swipe to dismiss — drag or flick a toast towards its screen edge, on touch, pen or mouse.
- Accessible — live-region roles, keyboard dismissal, focus styles,
prefers-reduced-motion. - Themeable — every colour, size and timing is a CSS custom property.
- SSR-safe — no timers are scheduled on the server.
- Zero runtime dependencies beyond
@angular/coreand@angular/common.
Installation
npm install @onedevio/og-toastQuick start
Render the container once, near the root of the app:
import { Component, inject } from '@angular/core';
import { OgToastComponent, OgToastService } from '@onedevio/og-toast';
@Component({
selector: 'app-root',
imports: [OgToastComponent],
template: `
<router-outlet />
<og-toast />
`,
})
export class App {
private readonly toast = inject(OgToastService);
save(): void {
this.toast.success('Your changes have been published.', 'Saved');
}
}That is all that is required — OgToastService is providedIn: 'root' and falls back to sensible defaults.
Configuration
Override the defaults once, at bootstrap:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideOgToast } from '@onedevio/og-toast';
bootstrapApplication(App, {
providers: [
provideOgToast({
position: 'bottom-center',
duration: 5000,
maxToasts: 3,
showProgress: true,
preventDuplicates: true,
}),
],
});| Option | Type | Default | Description |
| ----------------------- | ---------------------------------- | ------------------------ | -------------------------------------------------------------------------------- |
| position | OgToastPositionInput | 'top-right' | Default viewport anchor. Accepts 'top-right' or { vertical, horizontal }. |
| duration | number | 4000 | Auto-dismiss delay in ms. 0 keeps the toast until dismissed. |
| maxToasts | number | 5 | Maximum toasts on screen; the oldest is dismissed first. 0 disables the limit. |
| dismissible | boolean | true | Clicking the toast body dismisses it. |
| closable | boolean | true | Render the close button. |
| pauseOnHover | boolean | true | Hover or focus pauses the auto-dismiss timer. |
| showIcon | boolean | true | Render the type icon. |
| showProgress | boolean | false | Render a progress bar for the remaining time. |
| ariaLive | 'polite' \| 'assertive' \| 'off' | 'polite' | Announcement politeness; errors always default to 'assertive'. |
| preventDuplicates | boolean | false | Restart the existing toast instead of stacking an identical one. |
| newestOnTop | boolean | false | Insert new toasts nearest the viewport edge. |
| exitAnimationDuration | number | 200 | Leave-animation length; keep in sync with --og-toast-exit-duration. |
| ariaLabel | string | 'Notifications' | Accessible label of the toast region. |
| closeAriaLabel | string | 'Dismiss notification' | Accessible label of the close button. |
Everything except maxToasts, preventDuplicates, newestOnTop, exitAnimationDuration, swipeThreshold and the two labels can also be set per toast.
Service API
success(message, title?, options?): OgToastRef;
error(message, title?, options?): OgToastRef;
warning(message, title?, options?): OgToastRef;
info(message, title?, options?): OgToastRef;
loading(message, title?, options?): OgToastRef; // sticky until updated or dismissed
show(config: OgToastConfig): OgToastRef; // full control
promise(source, messages, options?): OgToastRef; // loading → success/error
update(id: string, changes: OgToastUpdate): void;
invokeAction(id: string): void;
dismiss(id: string, reason?): void; // animates the toast out
dismissAll(reason?): void;
clear(): void; // removes everything immediately
getRef(id: string): OgToastRef | undefined;
readonly toasts: Signal<readonly OgToast[]>;
readonly groups: Signal<readonly OgToastGroup[]>;
readonly count: Signal<number>;
readonly exiting: Signal<ReadonlySet<string>>;
readonly paused: Signal<ReadonlySet<string>>;The third argument of the helpers is either a duration in milliseconds or a full options object:
toast.error('The upload failed.', 'Error', 8000);
toast.info('Deploy started.', undefined, {
duration: 0, // sticky until dismissed
position: 'bottom-left',
dismissible: false,
showProgress: true,
cssClass: 'deploy-toast',
});Toast references
Every open method returns an OgToastRef:
const ref = toast.loading('Uploading…');
ref.update({ type: 'success', message: 'Uploaded', duration: 3000 });
ref.pause();
ref.resume();
ref.dismiss();
ref.toast(); // Signal<OgToast | undefined> — undefined once removed
ref.dismissed(); // Signal<boolean>
await ref.afterDismissed(); // 'timeout' | 'user' | 'action' | 'programmatic' | 'limit' | 'clear'update() leaves absent fields untouched. The content fields — title, message, action, template, component and data — can be blanked out by passing undefined explicitly.
Async feedback
toast.promise(api.save(draft), {
loading: 'Saving…',
success: (saved) => `Saved “${saved.title}”`,
error: (err) => ({ title: 'Could not save', message: String(err) }),
});A loading toast appears immediately and turns into a success or error toast when the promise settles. The promise itself is left untouched, so the caller stays responsible for handling its rejection.
Action buttons
toast.info('Draft moved to trash.', 'Deleted', {
action: {
label: 'Undo',
handler: (ref) => restore(ref.id),
dismissOnAction: true, // default
},
});Swipe to dismiss
Toasts can be flicked away with a pointer, travelling towards the edge they are anchored to: a
top-right toast swipes right, bottom-left swipes left, and a centred stack swipes up or down.
A drag dismisses once it passes swipeThreshold, or earlier if it is fast enough to read as a flick.
provideOgToast({ swipeToDismiss: true, swipeThreshold: 100 });
toast.error('Payment declined.', 'Error', { swipeToDismiss: false }); // must be acknowledgedThe gesture pauses the auto-dismiss timer while the pointer is down, claims only the axis it needs
(touch-action: pan-y for a horizontal swipe, so the page still scrolls), never fires a stray click
on release, and reports 'swipe' to afterDismissed().
Custom content
Render your own template:
<ng-template #release let-toast let-ref="ref" let-data="data">
<p>Release {{ data.version }} is live</p>
<button type="button" (click)="ref.dismiss()">Got it</button>
</ng-template>toast.show({ type: 'info', template: this.release(), data: { version: '2.4.0' } });…or your own component, which can read its payload and drive its own toast:
@Component({/* … */})
export class InviteToast {
protected readonly invite = injectOgToastData<Invite>();
private readonly ref = inject(OG_TOAST_REF);
accept(): void {
this.ref.update({ type: 'success', component: undefined, message: 'Joined!' });
}
}
toast.show({ type: 'info', component: InviteToast, data: invite, duration: 0 });data is deliberately typed as unknown on the toast pipeline; injectOgToastData<T>() is where you name its type.
Theming
The component exposes CSS custom properties. Set them on og-toast, on :root, or on any ancestor:
og-toast {
--og-toast-radius: 16px;
--og-toast-max-width: 420px;
--og-toast-inset: 32px;
--og-toast-success-bg: #052e21;
--og-toast-success-fg: #d1fae5;
--og-toast-success-border: #34d399;
}Available groups: layout (--og-toast-z-index, --og-toast-inset, --og-toast-gap, --og-toast-min-width, --og-toast-max-width, --og-toast-padding, --og-toast-radius), typography (--og-toast-font-family, --og-toast-title-size, --og-toast-message-size, --og-toast-icon-size), motion (--og-toast-enter-duration, --og-toast-exit-duration, --og-toast-enter-easing, --og-toast-exit-easing), elevation (--og-toast-shadow, --og-toast-shadow-hover), progress (--og-toast-progress-height, --og-toast-progress-opacity) and per type --og-toast-{success|error|warning|info|loading}-{bg|border|fg|icon}.
A dark palette is applied automatically under prefers-color-scheme: dark; overriding the variables yourself takes precedence.
Accessibility
- Each stack is a
role="region"labelled byariaLabel. ariaLive: 'assertive'rendersrole="alert",'polite'rendersrole="status",'off'renders neither. Errors default to assertive.- Dismissible toasts are focusable and respond to Enter, Space and Escape.
- The close button and action button are real
<button>s with accessible labels and visible focus rings. - Hover and keyboard focus pause the auto-dismiss timer, so toasts do not disappear mid-read.
- Swipe is an addition to, never a replacement for, the close button and keyboard dismissal.
- Animations collapse to a plain fade under
prefers-reduced-motion: reduce.
Server-side rendering
The service detects a non-browser platform and skips all timers, so toasts created during SSR render once and never block hydration or leave pending macrotasks.
Package layout
| Export | Purpose |
| ------------------------------------------------------ | --------------------------------------------------------------- |
| OgToastComponent | The <og-toast /> container. Renders one region per position. |
| OgToastItemComponent | A single toast. Presentational; exported for custom containers. |
| OgToastIconComponent | The type glyph, including the loading spinner. |
| OgToastService | Creates and tracks toasts. |
| OgToastRef | Handle to one toast. |
| provideOgToast / OG_TOAST_CONFIG | Application-wide defaults. |
| OG_TOAST_REF / OG_TOAST_DATA / injectOgToastData | Injection surface for custom toast components. |
License
MIT
