Toast
FreeImperative 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 demoMore examples
With an action
example.tsTypeScript
5 lines
toast.show({
variant: 'info',
message: 'Item archived',
action: { label: 'Undo', handler: () => restore() },
});A retry loop that fails eleven times
example.tsTypeScript
18 lines
// 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
13 lines
// 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
| Name | Type | Default | Description |
|---|---|---|---|
| position | ToastPosition | 'top-right' | Where the stack is anchored on screen. |
| maxVisible | number | 5 | Maximum number of toasts visible simultaneously. |
| portal | boolean | false | Move 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.