ngx-signal-toast
v1.1.4
Published
Angular 21 Signals-based toast — zero animation dependency
Readme
███╗ ██╗ ██████╗ ██╗ ██╗ ███████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██╗
████╗ ██║██╔════╝ ╚██╗██╔╝ ██╔════╝██║██╔════╝ ████╗ ██║██╔══██╗██║
██╔██╗ ██║██║ ███╗ ╚███╔╝ █████╗███████╗██║██║ ███╗██╔██╗██║███████║██║
██║╚██╗██║██║ ██║ ██╔██╗ ╚════╝╚════██║██║██║ ██║██║╚████║██╔══██║██║
██║ ╚████║╚██████╔╝██╔╝ ██╗ ███████║██║╚██████╔╝██║ ╚███║██║ ██║███████╗
╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚══╝╚═╝ ╚═╝╚══════╝
████████╗ ██████╗ █████╗ ███████╗████████╗
██║ ██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝
██║ ██║ ██║███████║███████╗ ██║
██║ ██║ ██║██╔══██║╚════██║ ██║
██║ ╚██████╔╝██║ ██║███████║ ██║
╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝The most powerful Angular 21 toast library — built entirely on Signals
Zero @angular/animations · Zero Zone.js dependency · Zero HTML tags to add
🚀 Quick Start · 📖 API Reference · 🎨 Themes · 💡 Examples · ⚙️ Configuration
📋 Table of Contents
- Why ngx-signal-toast?
- Features
- Version & Requirements
- Installation
- Quick Start
- Basic Usage
- Toast Types
- Configuration
- All Options Reference
- Themes
- Layouts
- Positions
- Animations
- Toast Actions
- Promise Toast
- Loading Toast
- Custom Icons
- Custom Styles
- ToastRef — Update & Dismiss
- CSS Variables
- SSR Support
- Accessibility
- Examples
- Changelog
🤔 Why ngx-signal-toast?
🆚 Comparison with Other Toast Libraries
| Feature | ngx-signal-toast | ngx-toastr | hot-toast |
|---|:---:|:---:|:---:|
| Angular 21 native | ✅ | ❌ | ❌ |
| Signals-first (no RxJS) | ✅ | ❌ | ❌ |
| Zoneless compatible | ✅ | ❌ | ❌ |
| @angular/animations free | ✅ | ❌ | ❌ |
| Auto-inject container | ✅ | ❌ | ❌ |
| 8 built-in themes | ✅ | ❌ | ❌ |
| 6 layout styles | ✅ | ❌ | ❌ |
| 9 positions | ✅ | ❌ | ❌ |
| Native CSS animations | ✅ | ❌ | ❌ |
| SSR safe | ✅ | ⚠️ | ⚠️ |
| Zero dependencies | ✅ | ❌ | ❌ |
| Promise API | ✅ | ❌ | ✅ |
| TypeScript 5.5+ | ✅ | ⚠️ | ⚠️ |
✨ Features
- 🎯 100% Signals — built with
signal(),computed(),effect()— no RxJS at all - ⚡ Zoneless Ready — works perfectly with Angular's new zoneless change detection
- 🎨 8 Beautiful Themes — Default, Glassmorphism, Neumorphism, Aurora, Neon, Luxury, Material, Brutalist
- 📐 6 Layout Styles — Default, Compact, Card, Pill, Sidebar, Banner
- 📍 9 Positions — all corners, edges, and center
- 🎬 5 Animation Presets — Slide, Fade, Bounce, Zoom, Flip — all via native CSS (no
@angular/animations) - 🔄 Promise API — loading → success/error in one call
- ⏸️ Pause on Hover — progress bar pauses when user hovers
- 🎯 Action Buttons — add interactive buttons inside any toast
- 📝 Update in Flight — update message/type/duration of an active toast
- 🌍 RTL Support — full right-to-left text support
- ♿ ARIA compliant —
role="alert",aria-live,aria-labelbuilt-in - 🖥️ SSR Safe —
isPlatformBrowserguards throughout - 🎭 Custom Icons — emoji, component, or template ref
- 🎨 Custom Body — inject your own component as toast body
- 🔗 Auto-inject — no
<nst-toast-container>HTML tag needed - 📦 ~0 extra dependencies — only
@angular/coreand@angular/common
📌 Version & Requirements
| Package | Version |
|---|---|
| ngx-signal-toast | 1.1.0 |
| @angular/core | >= 21.0.0 |
| @angular/common | >= 21.0.0 |
| TypeScript | >= 5.5 |
| Node.js | >= 20.0.0 |
⚠️ Angular 21 only. This library uses APIs introduced in Angular 21 (
provideAppInitializer, signals-first DI). It will not work on older versions.
📦 Installation
# npm
npm install ngx-signal-toast
# yarn
yarn add ngx-signal-toast
# pnpm
pnpm add ngx-signal-toastUsing ng-add (recommended)
ng add ngx-signal-toastThe schematic will automatically:
- Add
provideNgxSignalToast()to yourapp.config.ts - Ask you to choose a default theme and position
🚀 Quick Start
Step 1 — Register the provider
Open app.config.ts and add provideNgxSignalToast():
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideNgxSignalToast } from 'ngx-signal-toast';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideNgxSignalToast({
position: 'top-right',
duration: 4000,
theme: 'default',
}),
],
};✅ That's it. No
<nst-toast-container>tag needed anywhere. The container is automatically injected intodocument.body.
Step 2 — Inject and use
// any.component.ts
import { Component, inject } from '@angular/core';
import { ToastService } from 'ngx-signal-toast';
@Component({
selector: 'app-root',
standalone: true,
template: `<button (click)="showToast()">Show Toast</button>`,
})
export class AppComponent {
private toast = inject(ToastService);
showToast() {
this.toast.success('Operation completed successfully!');
}
}🔧 Basic Usage
import { Component, inject } from '@angular/core';
import { ToastService } from 'ngx-signal-toast';
@Component({ ... })
export class MyComponent {
private toast = inject(ToastService);
// Basic types
showSuccess() { this.toast.success('Saved successfully!'); }
showError() { this.toast.error('Something went wrong.'); }
showWarning() { this.toast.warning('Please review your input.'); }
showInfo() { this.toast.info('Update available.'); }
showLoading() { this.toast.loading('Loading your data...'); }
// With a title
showWithTitle() {
this.toast.success('Your file has been uploaded.', {
title: 'Upload Complete',
});
}
// Custom duration (milliseconds)
showLong() {
this.toast.info('This stays for 10 seconds.', { duration: 10000 });
}
// Permanent (never auto-dismiss)
showPermanent() {
this.toast.warning('Action required.', { duration: 0 });
}
}🎯 Toast Types
| Method | Icon | Default Color | Use Case |
|---|---|---|---|
| toast.success(msg) | ✓ checkmark | Green #22c55e | Operation completed |
| toast.error(msg) | ✗ cross | Red #f43f5e | Something failed |
| toast.warning(msg) | ⚠ triangle | Amber #f59e0b | Needs attention |
| toast.info(msg) | ℹ circle | Blue #3b82f6 | Informational |
| toast.loading(msg) | ⟳ spinner | Purple #8b5cf6 | Async operation |
| toast.show(msg, opts) | ★ star | Pink #ec4899 | Custom / flexible |
⚙️ Configuration
Pass options to provideNgxSignalToast() to set global defaults. Every option can be overridden per-toast.
provideNgxSignalToast({
position: 'top-right', // where toasts appear
duration: 4000, // auto-dismiss in ms (0 = never)
theme: 'default', // visual theme
layout: 'default', // layout style
animation: 'slide', // enter/leave animation
maxToasts: 5, // max visible at once
showProgress: true, // show countdown bar
pauseOnHover: true, // pause timer on hover
closeOnClick: false, // dismiss on toast click
closable: true, // show close button
dedupe: false, // prevent duplicate messages
rtl: false, // right-to-left mode
zIndex: 9999, // CSS z-index of container
})📚 All Options Reference
Global Config Options (provideNgxSignalToast)
| Option | Type | Default | Description |
|---|---|---|---|
| position | ToastPosition | 'top-right' | Where toasts appear on screen |
| duration | number | 4000 | Auto-dismiss time in ms. 0 = never |
| theme | ToastTheme | 'default' | Visual theme |
| layout | ToastLayout | 'default' | Layout style |
| animation | ToastAnimationPreset | 'slide' | Enter/leave animation |
| maxToasts | number | 5 | Max toasts visible at once |
| showProgress | boolean | true | Show countdown progress bar |
| pauseOnHover | boolean | true | Pause timer when hovering |
| closeOnClick | boolean | false | Dismiss when toast is clicked |
| closable | boolean | true | Show the × close button |
| dedupe | boolean | false | Suppress duplicate messages |
| rtl | boolean | false | Right-to-left text direction |
| zIndex | number | 9999 | CSS z-index of toast container |
Per-Toast Options (ToastOptions)
All global options above plus:
| Option | Type | Default | Description |
|---|---|---|---|
| id | string | auto | Custom ID for the toast |
| title | string | '' | Bold title above the message |
| type | ToastType | 'info' | Toast type (success/error/warning/info/loading/custom) |
| group | string | '' | Group name for bulk dismiss |
| hideIcon | boolean | false | Hide the type icon |
| iconEmoji | string | — | Show an emoji as icon |
| iconComponent | Type<any> | — | Angular component as icon |
| iconTemplate | TemplateRef | — | Template ref as icon |
| bodyComponent | Type<any> | — | Replace entire body with component |
| ariaLabel | string | message | Accessibility label |
| data | any | — | Attach any custom data |
| styles | ToastCustomStyles | — | Per-toast style overrides |
| actions | ToastAction[] | [] | Action buttons inside toast |
| onOpen | () => void | — | Callback when toast appears |
| onClose | () => void | — | Callback when toast dismisses |
ToastCustomStyles
| Property | Type | Description |
|---|---|---|
| accentColor | string | Icon and accent color (hex/rgb/hsl) |
| background | string | Toast background color |
| borderColor | string | Border color |
| borderRadius | string \| number | Border radius (px or CSS string) |
| titleColor | string | Title text color |
| messageColor | string | Message text color |
| iconBackground | string | Icon wrapper background |
🎨 Themes
Set globally or per-toast with theme: 'theme-name'.
| Theme | Description |
|---|---|
| default | Clean, modern dark card with subtle shadow |
| glassmorphism | Frosted glass with blur and transparency |
| neumorphism | Soft shadow depth, tactile feel |
| aurora | Gradient border, northern-lights inspired |
| neon | Glowing neon borders on dark background |
| luxury | Gold accents, premium feel |
| material | Google Material Design style |
| brutalist | Raw, bold, high-contrast editorial style |
// Per-toast theme override
this.toast.success('Uploaded!', { theme: 'glassmorphism' });
this.toast.error('Failed!', { theme: 'neon' });
this.toast.info('Note', { theme: 'luxury' });📐 Layouts
| Layout | Description |
|---|---|
| default | Standard card with icon, title, message |
| compact | Slim single-line with small icon |
| card | Colored top border accent bar |
| pill | Rounded pill shape, centered content |
| sidebar | Left colored stripe accent |
| banner | Full-width banner style |
this.toast.info('Compact notification', { layout: 'compact' });
this.toast.success('Card style!', { layout: 'card' });
this.toast.warning('Full width alert', { layout: 'banner' });📍 Positions
9 positions covering the entire viewport:
┌─────────────────────────────────────┐
│ top-left top-center top-right │
│ │
│ center-left center center-right │
│ │
│ bottom-left bottom-center bot-right │
└─────────────────────────────────────┘| Position | Value |
|---|---|
| Top Left | 'top-left' |
| Top Center | 'top-center' |
| Top Right | 'top-right' |
| Center Left | 'center-left' |
| Center | 'center' |
| Center Right | 'center-right' |
| Bottom Left | 'bottom-left' |
| Bottom Center | 'bottom-center' |
| Bottom Right | 'bottom-right' |
// Different positions per toast
this.toast.success('Top right', { position: 'top-right' });
this.toast.error('Bottom center', { position: 'bottom-center' });
this.toast.info('Center screen', { position: 'center' });🎬 Animations
All animations use native CSS — no @angular/animations package required.
| Preset | Enter | Leave |
|---|---|---|
| slide | Slides down from top + fade in | Slides up + fade out |
| fade | Fade in + slight scale | Fade out + scale down |
| bounce | Spring overshoot effect | Shrink + fade |
| zoom | Scale from 50% | Scale to 50% + fade |
| flip | 3D perspective rotate | Rotate back + fade |
this.toast.success('Bouncy!', { animation: 'bounce' });
this.toast.info('Flipping in', { animation: 'flip' });
this.toast.error('Zoomed', { animation: 'zoom' });Accessibility: All animations are automatically disabled when the user has
prefers-reduced-motion: reduceset in their OS settings.
🔘 Toast Actions
Add interactive buttons directly inside a toast:
this.toast.warning('Unsaved changes detected.', {
title: 'Confirm Action',
duration: 0,
actions: [
{
label: 'Save',
onClick: (id) => {
this.saveData();
// Toast auto-dismisses after action by default
},
},
{
label: 'Discard',
style: 'danger', // red danger style
onClick: (id) => {
this.discardChanges();
},
closeOnClick: true,
},
],
});ToastAction Options
| Property | Type | Default | Description |
|---|---|---|---|
| label | string | required | Button text |
| onClick | (id: string) => void | required | Click handler |
| style | 'default' \| 'danger' | 'default' | Button style |
| closeOnClick | boolean | true | Auto-dismiss on click |
🔄 Promise Toast
Show loading → automatically switch to success or error when the promise resolves:
// Basic promise
await this.toast.promise(
this.apiService.saveData(payload),
{
loading: 'Saving your data...',
success: 'Saved successfully!',
error: 'Failed to save. Please try again.',
}
);
// With dynamic messages based on result
await this.toast.promise(
this.userService.createUser(form),
{
loading: 'Creating account...',
success: (user) => `Welcome, ${user.name}!`,
error: (err) => `Error: ${err.message}`,
},
{
position: 'top-center',
theme: 'glassmorphism',
}
);⏳ Loading Toast
For long-running async operations you control manually:
// Show loading toast — returns a ToastRef
const ref = this.toast.loading('Uploading file...');
try {
await this.uploadService.upload(file, (progress) => {
// Update the message dynamically
ref.update({ message: `Uploading... ${progress}%` });
});
// Switch to success when done
ref.update({
type: 'success',
message: 'File uploaded successfully!',
duration: 4000, // now auto-dismiss
});
} catch (error) {
// Switch to error on failure
ref.update({
type: 'error',
message: 'Upload failed. Please try again.',
duration: 5000,
});
}🎭 Custom Icons
Emoji Icon
this.toast.success('Great job!', {
iconEmoji: '🎉',
});
this.toast.info('Check this out', {
iconEmoji: '👀',
});Angular Component as Icon
@Component({
standalone: true,
template: `<svg ...><!-- your SVG --></svg>`,
})
export class MyIconComponent {}
// Use it
this.toast.success('Custom icon!', {
iconComponent: MyIconComponent,
});Template Ref as Icon
<ng-template #starIcon>
<span class="material-icons">star</span>
</ng-template>@ViewChild('starIcon') starIcon!: TemplateRef<any>;
showToast() {
this.toast.success('Starred!', {
iconTemplate: this.starIcon,
});
}Hide Icon Entirely
this.toast.info('No icon here', { hideIcon: true });🖌️ Custom Styles
Override appearance per-toast using the styles option:
// Custom brand colors
this.toast.show('Custom branded toast!', {
type: 'custom',
styles: {
accentColor: '#FF6B6B',
background: '#1A1A2E',
borderColor: '#FF6B6B',
borderRadius: 20,
titleColor: '#FFFFFF',
messageColor: '#CCCCCC',
iconBackground: 'rgba(255,107,107,0.15)',
},
});
// Combine with emoji icon
this.toast.show('🚀 Deployment triggered!', {
type: 'custom',
title: 'CI/CD Pipeline',
iconEmoji: '🚀',
styles: {
accentColor: '#6C63FF',
background: '#0F0F23',
},
});🎯 ToastRef — Update & Dismiss
Every toast.*() method returns a ToastRef for programmatic control:
const ref = this.toast.info('Processing...', { duration: 0 });
// Update any property of the active toast
ref.update({
message: 'Almost done...',
type: 'success',
duration: 3000, // starts auto-dismiss countdown
});
// Dismiss programmatically
ref.dismiss();
// Check the ID
console.log(ref.id); // 'nst-1-1234567890'ToastRef API
| Method | Description |
|---|---|
| ref.id | Unique string ID of this toast |
| ref.update(patch) | Update any toast option on the fly |
| ref.dismiss() | Dismiss this toast immediately |
🗂️ Service Methods
// Show methods
toast.success(message, options?) // → ToastRef
toast.error(message, options?) // → ToastRef
toast.warning(message, options?) // → ToastRef
toast.info(message, options?) // → ToastRef
toast.loading(message, options?) // → ToastRef
toast.show(message, options?) // → ToastRef
// Promise helper
toast.promise(promise, messages, options?) // → Promise<T>
// Dismiss methods
toast.dismiss(id) // dismiss one by ID
toast.dismissAll() // dismiss all visible toasts
toast.dismissByType('error') // dismiss all of a type
toast.dismissByGroup('auth') // dismiss by group name
// State signals (read-only)
toast.toasts() // Signal<Toast[]>
toast.count() // Signal<number>
toast.isEmpty() // Signal<boolean>🎨 CSS Variables
Customize the look globally using CSS custom properties in your styles.css:
:root {
/* Layout */
--nst-gap: 10px; /* gap between stacked toasts */
/* Per-theme overrides */
--nst-bg: #1f2937;
--nst-color: #f9fafb;
--nst-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
--nst-radius: 14px;
--nst-border: none;
--nst-font: 'Your Font', system-ui, sans-serif;
}🖥️ SSR Support
ngx-signal-toast is fully SSR-safe. All browser APIs are guarded with isPlatformBrowser. The container is only injected in browser environments. No extra configuration needed for Angular Universal or any SSR setup.
// Works in SSR — library handles this internally
provideNgxSignalToast({ theme: 'default' })♿ Accessibility
| Feature | Implementation |
|---|---|
| ARIA roles | role="alert" for errors, role="status" for others |
| Live regions | aria-live="assertive" for errors, aria-live="polite" for others |
| Labels | aria-label on every toast (defaults to message text) |
| Close button | aria-label="Close notification" |
| Reduced motion | All animations disabled via @media (prefers-reduced-motion: reduce) |
| RTL support | Full direction: rtl support via rtl: true option |
| Focus management | Close button focusable with keyboard |
💡 Examples
E-commerce — Cart Notification
addToCart(product: Product) {
this.cartService.add(product);
this.toast.success(`${product.name} added to cart`, {
title: 'Cart Updated',
layout: 'card',
theme: 'default',
iconEmoji: '🛒',
duration: 3000,
actions: [{
label: 'View Cart',
onClick: () => this.router.navigate(['/cart']),
}],
});
}Auth — Login Flow
async login(credentials: LoginForm) {
await this.toast.promise(
this.authService.login(credentials),
{
loading: 'Signing you in...',
success: (user) => `Welcome back, ${user.firstName}!`,
error: 'Invalid email or password.',
},
{ position: 'top-center', theme: 'glassmorphism' }
);
}File Upload with Progress
async uploadFile(file: File) {
const ref = this.toast.loading(`Uploading ${file.name}...`, {
duration: 0,
closable: false,
});
try {
await this.fileService.upload(file, (pct) => {
ref.update({ message: `Uploading ${file.name}... ${pct}%` });
});
ref.update({ type: 'success', message: 'Upload complete!', duration: 3000 });
} catch {
ref.update({ type: 'error', message: 'Upload failed.', duration: 5000 });
}
}Bulk Dismiss by Group
// Show several related toasts with a group name
this.toast.info('Step 1 complete', { group: 'wizard' });
this.toast.info('Step 2 complete', { group: 'wizard' });
this.toast.info('Step 3 complete', { group: 'wizard' });
// Later dismiss them all at once
this.toast.dismissByGroup('wizard');RTL Support (Arabic / Hebrew)
// Set globally
provideNgxSignalToast({ rtl: true })
// Or per toast
this.toast.success('تم الحفظ بنجاح', { rtl: true });Custom Full Body Component
@Component({
standalone: true,
template: `
<div style="padding: 16px; display: flex; gap: 12px; align-items: center">
<img src="/avatar.png" width="40" height="40" style="border-radius: 50%" />
<div>
<strong>John sent you a message</strong>
<p style="margin: 0; opacity: 0.8; font-size: 13px">Hey, are you free today?</p>
</div>
</div>
`,
})
export class MessageToastComponent {}
// Use it
this.toast.show('', {
bodyComponent: MessageToastComponent,
duration: 6000,
theme: 'glassmorphism',
});Reading Toast State in Template
@Component({
template: `
@if (!toast.isEmpty()) {
<span>{{ toast.count() }} active notifications</span>
}
<button (click)="toast.dismissAll()">Clear All</button>
`
})
export class NotificationBarComponent {
toast = inject(ToastService);
}📦 TypeScript Types
import type {
Toast,
ToastRef,
ToastOptions,
ToastConfig,
ToastAction,
ToastCustomStyles,
ToastType, // 'success' | 'error' | 'warning' | 'info' | 'loading' | 'custom'
ToastPosition, // 'top-left' | 'top-center' | 'top-right' | ...
ToastTheme, // 'default' | 'glassmorphism' | 'neumorphism' | ...
ToastLayout, // 'default' | 'compact' | 'card' | 'pill' | 'sidebar' | 'banner'
ToastAnimationPreset // 'slide' | 'fade' | 'bounce' | 'zoom' | 'flip'
} from 'ngx-signal-toast';📝 Changelog
v1.1.0
- 🎬 Migrated from
@angular/animationsto native CSS animations - ✅ Removed
@angular/animationspeer dependency entirely - 🔧 Auto-inject container via
provideAppInitializer— no HTML tag needed - 🔁 Split
ToastStateServiceto resolve circular dependency (NG0200) - 🌀 Fixed spinner animation interrupted by mouse interaction
- ♿ Added
prefers-reduced-motionsupport
v1.0.0
- 🎉 Initial release
- 8 themes, 6 layouts, 9 positions, 5 animations
- Full Signals API, Promise helper, Toast actions
- SSR support, RTL support
🤝 Contributing
Contributions are welcome! Please open an issue first to discuss what you would like to change.
# Clone and install
git clone https://github.com/your-org/ngx-signal-toast.git
cd ngx-signal-toast
npm install
# Build the library
ng build ngx-signal-toast --configuration production
# Run the demo
ng serve demo
# Run tests
ng test ngx-signal-toast📄 License
MIT © 2025 ngx-signal-toast
Made with ❤️ for the Angular community
⭐ Support the Project
If you find this library useful:
⭐ Star the repository
🐛 Report issues
💡 Suggest features
🤝 Contribute improvements
