Skip to content
Ndop UI

Toast

A brief message about an action, shown in a corner without interrupting, such as Saved or Message sent.

import { Component, inject } from '@angular/core';
import { Button } from '@/ui/button/button';
import { Toaster } from '@/ui/toast/toast';

/**
 * A toast with a description and an Undo action. Toasts show in the
 * toast region your app's shell renders once (see the Toast page).
 */
@Component({
  selector: 'example-toast-demo',
  imports: [Button],
  template: `
    <button ndButton variant="outline" (click)="schedule()">
      Add to calendar
    </button>
  `,
})
export class ToastDemoExample {
  private readonly toaster = inject(Toaster);

  /** Shows the confirmation, with an Undo that says so. */
  protected schedule(): void {
    this.toaster.show({
      title: 'Event has been created',
      description: 'Sunday, December 3 at 9:00 AM',
      action: {
        label: 'Undo',
        onClick: () => this.toaster.show({ title: 'Event removed' }),
      },
    });
  }
}

A toast is a brief message about something that just happened, such as "Saved" or "Message sent", in a corner of the screen. Toasts stack, newest in front, and spread out while hovered or focused; they're announced politely by screen readers, without moving focus, and go away after four seconds unless the pointer or focus is on them. F8 jumps to them. Show one with inject(Toaster).show({ title: 'Saved' }), and put the region in your app's shell once, with toaster = inject(Toaster):

<ng-template ndToaster>
  <section ndToastRegion>
    <ol ndToastList>
      @for (toast of toaster.toasts(); track toast.id) {
      <li [ndToast]="toast">
        <div ndToastContent>
          <p ndToastTitle>{{ toast.title }}</p>
          @if (toast.description) {
          <p ndToastDescription>{{ toast.description }}</p>
          }
        </div>
        @if (toast.action; as action) {
        <button ndToastAction>{{ action.label }}</button>
        }
        <button ndToastClose aria-label="Close"><svg ndIcon="x"></svg></button>
      </li>
      }
    </ol>
  </section>
</ng-template>

Installation

npx @ndop/cli add toast

Usage

import { Button } from '@/ui/button/button';
import { Toaster } from '@/ui/toast/toast';
<button ndButton variant="outline" (click)="schedule()">
  Add to calendar
</button>

Examples

Toast types

Success, info, warning, error and loading toasts, the loading one replaced when it finishes.

import { Component, inject } from '@angular/core';
import { Button } from '@/ui/button/button';
import { Toaster, type ToastType } from '@/ui/toast/toast';

/**
 * Success, info, warning, error and loading toasts. The loading one is
 * replaced, under the same id, when the work finishes.
 */
@Component({
  selector: 'example-toast-types',
  imports: [Button],
  template: `
    <div class="flex flex-wrap gap-2">
      @for (type of types; track type.type) {
        <button
          ndButton
          variant="outline"
          (click)="show(type.type, type.title)"
        >
          {{ type.label }}
        </button>
      }
      <button ndButton variant="outline" (click)="load()">Loading</button>
    </div>
  `,
})
export class ToastTypesExample {
  private readonly toaster = inject(Toaster);

  protected readonly types: readonly {
    type: ToastType;
    label: string;
    title: string;
  }[] = [
    { type: 'success', label: 'Success', title: 'Changes saved' },
    { type: 'info', label: 'Info', title: 'A new version is available' },
    { type: 'warning', label: 'Warning', title: 'Your storage is almost full' },
    { type: 'error', label: 'Error', title: "Couldn't upload the file" },
  ];

  /**
   * Shows a toast of a type.
   *
   * @param type - What it's about.
   * @param title - Its message.
   */
  protected show(type: ToastType, title: string): void {
    this.toaster.show({ type, title });
  }

  /** Shows a loading toast, then replaces it once the upload is done. */
  protected load(): void {
    const id = this.toaster.show({
      type: 'loading',
      title: 'Uploading report.pdf…',
      duration: Infinity,
    });
    setTimeout(
      () =>
        this.toaster.show({ id, type: 'success', title: 'Report uploaded' }),
      2000,
    );
  }
}

Accessibility

Implements the live region pattern of the WAI-ARIA Authoring Practices.

Keyboard

Keys the component handles.

Key Action
F8Focuses the toast region from anywhere on the page.
TabMoves through the toasts' buttons, then on to the page.
EscapeWith focus in a toast, dismisses it.

Focus

  • Toasts never take focus. F8 moves focus to the region; while it's there, nothing times out.
  • When a toast with focus inside is dismissed, focus moves to the region.

Right-to-left

Toasts swipe toward the end side, so to the left in right-to-left layouts. Direction comes from the nearest dir attribute.

API reference

ToasterOutlet

ng-template[ndToaster]

Renders the toast region in its template above everything else, once the app has hydrated. Put one in the app's shell.

ToastRegion

[ndToastRegion]role region

The toast region (@ndop/primitives/toast), in the corner at the end side, bottom: a polite live region and landmark that F8 focuses. Put it in an ng-template[ndToaster] once, in the app's shell, with an ol of the toasts in NdToaster.toasts(). Hovering it or focusing inside it pauses the toasts and spreads the stack out.

Manages aria-label, aria-live, aria-relevant.

ToastRegion binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoaster
data-expanded —

ToastList

ol[ndToastList]

The list of toasts: stacked on top of each other, newest in front, and spread out while its region is hovered or focused.

ToastList binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-list

Toast

[ndToast]

One toast, styled like Sonner's: in the stack, older toasts sit slightly behind and above the newest, and only three show until the stack spreads out. It follows a swipe toward the end side, which dismisses it past 50 pixels.

Manages aria-atomic.

Toast binding Type Default
[class]
Extra classes, merged last so they win.
string''
[ndToast]required
The toast's record.
NdToastRecord—
Data attribute Values
data-slottoast
data-type —
data-index —
data-front —
data-swiping —

ToastContent

[ndToastContent]

Stacks a toast's title and description.

ToastContent binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-content

ToastTitle

[ndToastTitle]

A toast's message.

ToastTitle binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-title

ToastDescription

[ndToastDescription]

A toast's detail, under its title.

ToastDescription binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-description

ToastAction

button[ndToastAction]

The button of a toast's action, such as Undo: it runs the action, then dismisses the toast.

ToastAction binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-action

ToastClose

button[ndToastClose]

The button that dismisses its toast, at its top start corner; name it, such as aria-label="Close", and put an icon in it.

ToastClose binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slottoast-close

Agent notes

Do

  • Keep it short, and say what happened: "Event created".
  • Offer Undo as its action for things that can be undone.
  • Use duration: Infinity for toasts people must act on.

Don't

  • Don't use a toast as the only place an error is shown: it disappears.
  • Don't show several at once for one action.