Toast

Show a brief message at the bottom of the screen that goes away by itself, without blocking the rest of the app.

Installation

npx @blotch/dolan add toast
import { UiToaster, toast } from '../components/ui/toast';

Setup

Put one <ui-toaster /> in your root component's template. It draws every toast in the app.

src/app/app.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { UiToaster } from '../components/ui/toast';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, UiToaster],
  template: `
    <router-outlet />
    <ui-toaster />
  `,
})
export class App {}

Usage

Call toast() from anywhere: a component, a service, a route guard or a plain function. It needs no injection.

import { toast } from '../components/ui/toast';

protected save(): void {
  toast({ title: 'Event has been created' });
}
  • The newest toast appears in front, above the bottom safe area. Up to three stack, with older ones peeking out behind. More wait in a queue.
  • Each toast closes after duration (5 seconds). The countdown starts when it becomes visible, so queued toasts don't expire unseen.
  • Tapping the front toast closes it. Dragging it down more than 80px closes it too, and a shorter drag springs it back.
  • Taps elsewhere keep working while a toast is showing.
  • toast() only updates a signal, so it's safe to call inside a tap handler.

With a description

toast({
  title: 'Event has been created',
  description: 'Sunday, December 3 at 9:00 AM',
});

Destructive

toast({
  title: 'Something went wrong',
  description: 'There was a problem with your request.',
  variant: 'destructive',
});

With an action

An action adds a button on the right. Tapping it runs onAction, then closes the toast.

toast({
  title: 'Message sent',
  action: { label: 'Undo', onAction: () => this.undoSend() },
});

onAction runs inside the tap. To navigate from it, wrap the navigation in setTimeout (see the setTimeout rule).

Duration

toast({ title: 'Copied', duration: 2000 });

Dragging a toast pauses its countdown. Letting it spring back restarts the full duration.

Dismiss from code

toast() returns the toast's ID. Pass it to dismissToast() to remove the toast early.

import { dismissToast, toast } from '../components/ui/toast';

const id = toast({ title: 'Uploading…', duration: 60000 });
// later
dismissToast(id);

dismissToast() removes the toast at once, without the slide-out animation.

API

UiToaster

The component that draws the toasts. It has no inputs or outputs. Render one, in your root component.

toast(options)

function toast(options: ToastOptions): string;

Shows a toast and returns its ID.

OptionTypeDefaultDescription
titlestringRequiredThe main message.
descriptionstringundefinedExtra detail below the title.
variant'default' | 'destructive''default'Visual style.
durationnumber5000Time on screen in milliseconds, once visible.
actionToastActionundefinedA button with a label and an onAction callback.

dismissToast(id)

function dismissToast(id: string): void;

Removes the toast with that ID, whether it's showing or queued.

toasts

const toasts: WritableSignal<readonly ToastData[]>;

The queue, oldest first. Read it, for example to show a count. Change it with toast() and dismissToast().

Types

type ToastAction = {
  readonly label: string;
  readonly onAction: () => void;
};

type ToastData = {
  readonly id: string;
  readonly title?: string;
  readonly description?: string;
  readonly variant: 'default' | 'destructive';
  readonly duration: number;
  readonly action?: ToastAction;
};

type ToastOptions = Partial<Omit<ToastData, 'id'>> & { readonly title: string };