Toast

Free

Imperative toast stack with 4 variants, 6 anchor positions, action callbacks and an auto-dismiss timer.

</>sv-toast-host toastnotificationfeedback
import { SvToastHostComponent } from '@styloviz/toast';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

With an action

example.tsTypeScript
toast.show({
  variant: 'info',
  message: 'Item archived',
  action: { label: 'Undo', handler: () => restore() },
});

A retry loop that fails eleven times

example.tsTypeScript
// Deduplication is on by default. Eleven identical failures are one
// toast with a x11 badge and a countdown that restarts from the last
// one - not eleven toasts that evict every other message in a stack
// bounded at five.
for (const attempt of attempts) {
  try { await attempt(); }
  catch { this.toast.error('Sync failed'); }
}

// An explicit key deduplicates on the SUBJECT rather than the wording,
// so these two replace each other instead of stacking.
this.toast.info('Saving...', { dedupeKey: 'record-7', duration: 0 });
await this.save();
this.toast.success('Saved', { dedupeKey: 'record-7' });

// And opt a single call out when two identical sentences are genuinely
// two events the user must see separately.
this.toast.info('Row added', { dedupeKey: null });

A toast that is about the PAGE, not the action

example.tsTypeScript
// The default is scope: 'global', and it is the right default: the
// commonest toast in any app is raised BY the action that navigates,
// and it has to survive that navigation to be read at all.
await this.save();
this.toast.success('Invoice saved');       // survives
this.router.navigate(['/invoices']);

// A message about the view the user is looking at is stale the moment
// they leave it. Say so, and provideSvOverlayRouteClose() drops it on
// the next navigation.
this.toast.warning('3 filters could not be applied to this view', {
  scope: 'route',
});

When to use

  • Confirming a completed action transiently without interrupting the flow.
  • Non-blocking feedback: "Saved", "Copied", "Email sent".
  • For persistent status use an Alert Banner; for decisions, a Confirm Dialog.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
positionToastPosition'top-right'Where the stack is anchored on screen.
maxVisiblenumber5Maximum number of toasts visible simultaneously.
portalbooleanfalseMove the region to `<body>` so it stays usable above a modal. `inert` IS INHERITED AND CANNOT BE UNDONE BY A DESCENDANT. A modal makes the page behind it inert, and a host mounted inside the app shell is part of that page: its toasts are still announced (speech goes through the shared announcer) but an "Undo" button on one cannot be pressed until the dialog closes. Portalled, the region is a `<body>` child the stack keeps live at any depth. Off by default because it moves the region's DOM (and its `data-sv-toast-*` hooks) out of the host.

Customization & theming

  • Six anchor positions and four variants.
  • Action buttons and auto-dismiss duration.
  • Driven imperatively via ToastService.
  • Deduplication, on by default: a repeat refreshes the toast that is already up, increments a `×n` badge and restarts its countdown, instead of filling a bounded stack with eleven copies of one failure. It keeps its place and its id, so the caller's handle still works and a screen reader is not read the same sentence twice. Key it yourself with `dedupeKey` (or `null` to opt out).
  • Route scope: a toast survives navigation by default, and `{ scope: 'route' }` marks one as belonging to the page it was raised on. `provideSvOverlayRouteClose()` (from `@styloviz/layout`) drops those and only those on `NavigationEnd`; `ToastService.navigated()` is the router-agnostic call behind it, for an app on a different router.
  • A bounded stack: `maxVisible` (validated — anything below 1 is 1, `Infinity` means no cap) shows the newest toasts and a "+N more" line counts the older ones held back; they stay alive and slide back into view as newer ones go. A toast raised while the pointer or focus is on the stack is paused on arrival, and a keyed replacement (`Saving…` then `Saved` on one `dedupeKey`) is announced — only an identical repeat is silent.
  • `[portal]="true"` moves the region into a `<body>` portal. Use it when toasts carry actions and your app opens modals: a modal makes the page behind it `inert`, and `inert` is inherited, so a region inside the app shell can still be heard (speech goes through the shared announcer) but its Undo button cannot be pressed until the dialog closes. Portalled, the region is a `<body>` child the overlay stack keeps live. Off by default because it moves the region out of the host element.

Best practices

  • Do: Mount exactly one `<sv-toast-host>` near the app root, then call the service.
  • Do: Offer an Undo action for destructive operations instead of a confirm prompt.
  • Don't: Don't put critical, must-read information only in a toast — it disappears.
  • Do: Give a repeated message an explicit `dedupeKey` when the wording changes but the subject does not — `{ dedupeKey: 'record-7' }` makes "Saving…" and "Saved" replace each other instead of stacking. Deduplication is on by default and keyed on variant + title + message + position; `{ dedupeKey: null }` opts a call out.
  • Do: Pass `{ scope: 'route' }` for a message ABOUT the current page, and leave the default `'global'` for the outcome of an action. A confirmation raised by a save that navigates must survive the navigation it caused; a note about a view the user has left is worse than nothing two pages later.

Accessibility

  • One shared live region announces every toast (`SvLiveAnnouncer`) — assertively for `error`, politely for the rest, including `warning`.
  • ⚠️ A toast is NOT a live region of its own any more, and that is a fix rather than a removal: each toast used to be inserted with `aria-live` and its text already inside it, which AT is least likely to read, and it then took its own region away with it four seconds later, mid-sentence. The stack container stays `role="region"` + `aria-label="Notifications"`, so a screen reader user can navigate to a toast and press its action.
  • Auto-dismiss pauses while the pointer OR keyboard focus is over the stack — both, separately — and resumes only when neither holds (WCAG 2.2.1); actions are keyboard reachable before dismiss.

Feedback on Toast

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.