Primitives
A toast is a non-modal, unobtrusive window element used to display brief, auto-expiring messages to the user.
import { Component, inject, TemplateRef } from "@angular/core";
import { NgpToast, NgpToastManager } from "ng-primitives/toast";
@Component({
selector: "app-toast",
imports: [NgpToast],
template: `
<button class="toast-trigger" (click)="show(toast)" ngpButton>
Show Toast
</button>
<ng-template #toast let-dismiss="dismiss">
<div ngpToast>
<p class="toast-title">This is a toast message</p>
<p class="toast-description">It will disappear in 3 seconds</p>
<button class="toast-dismiss" (click)="dismiss()" ngpButton>
Dismiss
</button>
</div>
</ng-template>
`,
styles: `
.toast-trigger {
padding-left: 1rem;
padding-right: 1rem;
border-radius: 0.5rem;
color: var(--ngp-text-primary);
border: none;
outline: none;
height: 2.5rem;
font-weight: 500;
background-color: var(--ngp-background);
transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--ngp-button-shadow);
}
.toast-trigger[data-hover] {
background-color: var(--ngp-background-hover);
}
.toast-trigger[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
.toast-trigger[data-press] {
background-color: var(--ngp-background-active);
}
[ngpToast] {
position: absolute;
touch-action: none;
transition:
transform 0.4s,
opacity 0.4s,
height 0.4s,
box-shadow 0.2s;
box-sizing: border-box;
align-items: center;
gap: 6px;
display: inline-grid;
background: var(--ngp-background);
box-shadow: var(--ngp-shadow);
border: 1px solid var(--ngp-border);
padding: 12px 16px;
opacity: 0;
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
border-radius: 8px;
z-index: var(--ngp-toast-z-index);
grid-template-columns: 1fr auto;
grid-template-rows: min-content min-content;
column-gap: 12px;
align-items: center;
width: var(--ngp-toast-width);
height: fit-content;
transform: var(--y);
}
.toast-title {
color: var(--ngp-text-primary);
font-size: 0.75rem;
font-weight: 600;
margin: 0;
grid-column: 1 / 2;
grid-row: 1;
line-height: 16px;
user-select: none;
}
.toast-description {
font-size: 0.75rem;
margin: 0;
color: var(--ngp-text-secondary);
grid-column: 1 / 2;
grid-row: 2;
line-height: 16px;
user-select: none;
}
.toast-dismiss {
background: var(--ngp-background-inverse);
color: var(--ngp-text-inverse);
border: none;
border-radius: 8px;
padding: 4px 8px;
font-weight: 600;
font-size: 12px;
cursor: pointer;
grid-column: 2 / 3;
grid-row: 1 / 3;
max-height: 27px;
}
[ngpToast][data-position-x="end"] {
right: 0;
}
[ngpToast][data-position-x="start"] {
left: 0;
}
[ngpToast][data-position-y="top"] {
top: 0;
--lift: 1;
--lift-amount: calc(var(--lift) * var(--ngp-toast-gap));
--y: translateY(-100%);
}
[ngpToast][data-position-y="bottom"] {
bottom: 0;
--lift: -1;
--lift-amount: calc(var(--lift) * var(--ngp-toast-gap));
--y: translateY(100%);
}
[ngpToast][data-enter] {
opacity: 1;
--y: translateY(0);
}
[ngpToast][data-exit] {
opacity: 0;
--y: translateY(calc(calc(var(--lift) * var(--ngp-toast-gap)) * -1));
}
[ngpToast][data-visible="false"] {
opacity: 0;
pointer-events: none;
}
[ngpToast][data-expanded="true"]::after {
content: "";
position: absolute;
left: 0;
height: calc(var(--ngp-toast-gap) + 1px);
bottom: 100%;
width: 100%;
}
[ngpToast][data-expanded="false"][data-front="false"] {
--scale: var(--ngp-toasts-before) * 0.05 + 1;
--y: translateY(calc(var(--lift-amount) * var(--ngp-toasts-before)))
scale(calc(-1 * var(--scale)));
height: var(--ngp-toast-front-height);
}
[ngpToast][data-expanded="true"] {
--y: translateY(calc(var(--lift) * var(--ngp-toast-offset)));
height: var(--ngp-toast-height);
}
[ngpToast][data-swiping="true"] {
transform: var(--y) translateY(var(--ngp-toast-swipe-amount-y, 0))
translateX(var(--ngp-toast-swipe-amount-x, 0));
transition: none;
}
[ngpToast][data-swiping="true"][data-swipe-direction="left"] {
/* Fade out from -45px to -100px swipe */
opacity: calc(
1 - clamp(0, ((-1 * var(--ngp-toast-swipe-x, 0px)) - 45) / 55, 1)
);
}
[ngpToast][data-swiping="true"][data-swipe-direction="right"] {
/* Fade out from 45px to 100px swipe */
opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-x, 0px) - 45) / 55, 1));
}
[ngpToast][data-swiping="true"][data-swipe-direction="top"] {
/* Fade out from -45px to -100px swipe */
opacity: calc(
1 - clamp(0, ((-1 * var(--ngp-toast-swipe-y, 0px)) - 45) / 55, 1)
);
}
[ngpToast][data-swiping="true"][data-swipe-direction="bottom"] {
/* Fade out from 45px to 100px swipe */
opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-y, 0px) - 45) / 55, 1));
}
`,
})
export default class ToastExample {
private readonly toastManager = inject(NgpToastManager);
show(toast: TemplateRef<void>): void {
this.toastManager.show(toast, { placement: "bottom-end" });
}
}
Import the Toast primitives from ng-primitives/toast
.
import { NgpToast } from 'ng-primitives/toast';
Assemble the toast directives in your template.
<ng-template #toast>
<div ngpToast>...</div>
</ng-template>
To show a toast, inject the NgpToastManager
service and call the show
method passing the toast template or a component
class that uses the NgpToast
directive as a Host Directive.
import { NgpToastManager } from 'ng-primitives/toast';
export class MyComponent {
private readonly toastManager = inject(NgpToastManager);
showToast(): void {
this.toastManager.show(toastTemplate);
// or
this.toastManager.show(MyToastComponent);
}
}
Create a toast component that uses the NgpToast
directive.
import { Component, inject } from '@angular/core';
import { NgpToastManager } from 'ng-primitives/toast';
import { Toast } from './toast';
@Component({
selector: 'app-root',
template: `
<button class="toast-trigger" (click)="show()" ngpButton>Show Toast</button>
`,
styles: `
.toast-trigger {
padding-left: 1rem;
padding-right: 1rem;
border-radius: 0.5rem;
color: var(--ngp-text-primary);
border: none;
outline: none;
height: 2.5rem;
font-weight: 500;
background-color: var(--ngp-background);
transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--ngp-button-shadow);
}
.toast-trigger[data-hover] {
background-color: var(--ngp-background-hover);
}
.toast-trigger[data-focus-visible] {
outline: 2px solid var(--ngp-focus-ring);
outline-offset: 2px;
}
.toast-trigger[data-press] {
background-color: var(--ngp-background-active);
}
`,
})
export default class App {
private readonly toastManager = inject(NgpToastManager);
show(): void {
this.toastManager.show(Toast, {
context: {
header: 'Toast Title',
description: 'This is a description of the toast notification.',
},
});
}
}
Generate a reusable toast component using the Angular CLI.
ng g ng-primitives:primitive toast
path
: The path at which to create the component file.prefix
: The prefix to apply to the generated component selector.componentSuffix
: The suffix to apply to the generated component class name.fileSuffix
: The suffix to apply to the generated component file name. Defaults to component
.exampleStyles
: Whether to include example styles in the generated component file. Defaults to true
.The following directives are available to import from the ng-primitives/toast
package:
[ngpToast]
ngpToast
The following data attributes are applied to the first child of the ngpToast
ng-template:
Attribute | Description | Value |
---|---|---|
data-position-x |
The horizontal position of the toast | start , center , end |
data-position-y |
The vertical position of the toast | top , bottom |
data-visible |
Whether the toast is currently visible. This is based on how many toasts are shown compared to the maxToasts set in the global config. |
true , false |
data-front |
Whether the toast is the front-most toast in the stack. | true , false |
data-swiping |
Whether the toast is currently being swiped. | true , false |
data-swipe-direction |
The direction of the swipe gesture. | left , right , up , down |
data-expanded |
Whether the toast is currently expanded. This can be used to collapse or expand stacked toasts. | true , false |
The following CSS custom properties are available to the ngpToast
directive:
Property | Description |
---|---|
--ngp-toast-gap |
The gap between each toast. |
--ngp-toast-z-index |
The z-index of the toast. |
--ngp-toasts-before |
The number of toasts before this one. |
--ngp-toast-index |
The index of the toast (1-based). |
--ngp-toast-width |
The width of the toast in pixels. |
--ngp-toast-height |
The height of the toast in pixels. |
--ngp-toast-offset |
The vertical offset position of the toast. |
--ngp-toast-front-height |
The height of the front-most toast. |
--ngp-toast-swipe-amount-x |
The swipe amount on the X axis (pixel value). |
--ngp-toast-swipe-amount-y |
The swipe amount on the Y axis (pixel value). |
--ngp-toast-swipe-x |
The swipe value on the X axis. |
--ngp-toast-swipe-y |
The swipe value on the Y axis. |
You can configure the default options for all toasts in your application by using the provideToastConfig
function in a providers array.
import { provideToastConfig } from 'ng-primitives/toast';
bootstrapApplication(AppComponent, {
providers: [
provideToastConfig({
placement: 'top-end',
duration: 5000,
width: 300,
offsetTop: 16,
offsetBottom: 16,
offsetLeft: 16,
offsetRight: 16,
dismissible: true,
maxToasts: 3,
zIndex: 9999999,
swipeDirections: ['left', 'right'],
ariaLive: 'assertive',
gap: 16,
}),
],
});
The default placement of the toast. Can be one of `top-start`, `top-end`, `top-center`, `bottom-start`, `bottom-end`, or `bottom-center`.
The duration in milliseconds that the toast will be visible.
The width of each toast in pixels.
The offset from the top of the viewport in pixels.
The offset from the bottom of the viewport in pixels.
The offset from the left of the viewport in pixels.
The offset from the right of the viewport in pixels.
Whether a toast can be dismissed by swiping.
The amount a toast must be swiped before it is considered dismissed.
The default swipe directions supported by the toast.
The maximum number of toasts that can be displayed at once.
The aria live setting.
The gap between each toast.
The z-index of the toast container.
Copyright © 2025 Angular Primitives