ββββ βββ βββββββ βββ βββ βββββββββββ βββββββ ββββ βββ ββββββ βββ
βββββ βββββββββββ ββββββββ βββββββββββββββββββ βββββ ββββββββββββββ
ββββββ ββββββ ββββ ββββββ ββββββββββββββββββββ ββββββββββββββββββββββββ
βββββββββββββ βββ ββββββ ββββββββββββββββββββ βββββββββββββββββββββββ
βββ βββββββββββββββββββ βββ βββββββββββββββββββββββ ββββββββ βββββββββββ
βββ βββββ βββββββ βββ βββ βββββββββββ βββββββ βββ βββββββ βββββββββββ
βββββββββ βββββββ ββββββ βββββββββββββββββ
βββ ββββββββββββββββββββββββββββββββββ
βββ βββ βββββββββββββββββββ βββ
βββ βββ βββββββββββββββββββ βββ
βββ ββββββββββββ βββββββββββ βββ
βββ βββββββ βββ βββββββββββ βββ
Zero @angular/animations Β· Zero Zone.js dependency Β· Zero HTML tags to add
π Quick Start Β· π API Reference Β· π¨ Themes Β· π‘ Examples Β· βοΈ Configuration
- 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
| 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+ | β |
- π― 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
| 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.
# npm
npm install ngx-signal-toast
# yarn
yarn add ngx-signal-toast
# pnpm
pnpm add ngx-signal-toastng add ngx-signal-toastThe schematic will automatically:
- Add
provideNgxSignalToast()to yourapp.config.ts - Ask you to choose a default theme and position
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.
// 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!');
}
}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 });
}
}| 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 |
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
})| 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 |
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 |
| 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 |
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' });| 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' });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' });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.
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,
},
],
});| 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 |
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',
}
);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,
});
}this.toast.success('Great job!', {
iconEmoji: 'π',
});
this.toast.info('Check this out', {
iconEmoji: 'π',
});@Component({
standalone: true,
template: `<svg ...><!-- your SVG --></svg>`,
})
export class MyIconComponent {}
// Use it
this.toast.success('Custom icon!', {
iconComponent: MyIconComponent,
});<ng-template #starIcon>
<span class="material-icons">star</span>
</ng-template>@ViewChild('starIcon') starIcon!: TemplateRef<any>;
showToast() {
this.toast.success('Starred!', {
iconTemplate: this.starIcon,
});
}this.toast.info('No icon here', { hideIcon: true });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',
},
});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'| 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 |
// 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>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;
}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' })| 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 |
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']),
}],
});
}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' }
);
}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 });
}
}// 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');// Set globally
provideNgxSignalToast({ rtl: true })
// Or per toast
this.toast.success('ΨͺΩ
Ψ§ΩΨΩΨΈ Ψ¨ΩΨ¬Ψ§Ψ', { rtl: true });@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',
});@Component({
template: `
@if (!toast.isEmpty()) {
<span>{{ toast.count() }} active notifications</span>
}
<button (click)="toast.dismissAll()">Clear All</button>
`
})
export class NotificationBarComponent {
toast = inject(ToastService);
}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';- π¬ 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
- π Initial release
- 8 themes, 6 layouts, 9 positions, 5 animations
- Full Signals API, Promise helper, Toast actions
- SSR support, RTL support
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-toastMIT Β© 2025 ngx-signal-toast