Toast
Transient, non-blocking feedback about something that already happened ("Saved", "Upload failed"). If the user MUST see or act on it, it is not a toast — use Notice (inline) or Dialog (blocking).
Added 14 October 2026
Toast, Notice, or Dialog?
Decide by how much the user must not miss it:
- Confirmation or status of a completed action, safe to miss, auto-dismisses →
Toast. It is announced once and disappears; treat it as a courtesy, never as the only record of an outcome. [a11y] - Information that must persist on the surface until resolved (form-level error, a
standing warning about the current state) →
Notice— it stays in the layout. - Something that must block the flow until acknowledged or decided (destructive
confirm, required input) →
Dialog. Never a toast — it can vanish before it is read.
Rule of thumb: if losing the message would cause a problem, it does not belong in a toast.
Setup — mount once, call from anywhere
Toast is imperative, not a component you place where it should appear. Mount a
single toaster at the app root, then trigger toasts from anywhere by calling toast().
React:
import { Toaster, toast } from '@stroeer-labs/design-library/react'
// once, at the app root:
<Toaster />
// anywhere:
toast.success('Campaign saved')
Angular:
import { HlmToasterImports, toast } from '@stroeer-labs/design-library/angular'
// once, at the app root component:
// imports: [HlmToasterImports]
// template: `<hlm-toaster />`
toast.success('Campaign saved')
One toaster per app. Mounting it inside a component that re-renders/repeats, or mounting several, gives duplicate or lost toasts. (The showcase mounts one per demo card only to isolate the two framework showcases — not a pattern to copy.)
Types — which one and when
Reach for the plain toast() by default; use a typed variant only when the type adds
real meaning (it also sets the icon). Type is carried by an icon and wording, never
by color alone. [a11y]
toast(msg)— neutral confirmation of a completed action (“Draft saved”).toast.success(msg)— an action succeeded and that success is worth calling out.toast.error(msg)— an action failed. Not for errors the user must fix before continuing — that is an inlineNoticeon the field/form. An error toast that carries a retry/action should be dismissable and not auto-dismiss, so it can actually be acted on.toast.warning(msg)— a completed action with a caveat (“Saved, but 2 rows skipped”).toast.info(msg)— incidental context; prefer silence if it adds nothing.toast.loading(msg)/toast.promise(p, {...})— pending async work. Prefertoast.promiseso the same toast transitions loading → success/error in place.
Content
- One line. A toast is a headline, not a paragraph. Put the essential outcome in the message.
description(optional) — a single supporting detail (timestamp, count). It renders muted below the message; do not hide anything load-bearing there. See docs/patterns/copy.md.action(optional) — a single reversal/next step, most often Undo. Keep it to one action; a toast is not a menu. The real, non-toast affordance must still exist elsewhere — the action can disappear with the toast. [a11y]
toast('Message sent', {
description: 'To 1,240 contacts',
action: { label: 'Undo', onClick: () => unsend() },
})
Config — sensible defaults, tune on the toaster
Set these once on <Toaster /> / <hlm-toaster />; defaults match across both
frameworks (position="bottom-right", duration=4000, visibleToasts=3,
closeButton=false, richColors=false).
position— where toasts stack (top-right,bottom-center, …). One position per app; do not scatter.duration— auto-dismiss ms. Auto-dismiss is in tension with WCAG 2.2.1 Timing Adjustable (Level A): a timed message the user needs must be pausable/extendable. So (a) toasts pause on hover/focus — sonner does this by default, keep it; (b) always leave a manual dismiss (closeButton); (c) a toast carrying anaction(Undo) must NOT rely on the ~4s default — give it a long duration orInfinityplus a close button, or move it to a Notice. Rough scale: ~4s plain, up to ~10s with an action. [a11y]closeButton— a manual dismiss. Enable it whenever a toast is long-lived, carries an action, or is an error, so users who can’t act within the auto-dismiss window still can (WCAG 2.2.1). [a11y]theme— follows the app’s.darkclass automatically (React: default; Angular:theme="auto").theme="system"follows the OS media query, NOT the app’s dark mode — do not use it to track the app theme.hotkey— focus-the-toast shortcut, same prop name on both sides (default ⌥/alt+T).
Cross-framework parity notes
- Same imperative API on both:
toast,toast.success/info/warning/error/loading/ promise/dismiss, same{ description, action }shape, same defaults. - Angular exposes a curated set of toaster inputs; React forwards the full sonner
ToasterProps. For per-toast tweaks both take atoastOptionsobject. - Icon set is fixed (lucide, one per type) and identical across frameworks; React lets
you override
icons, Angular does not. - Screen-reader announcement is engine-handled: non-urgent toasts announce politely
(
role="status"), errors assertively (role="alert"). You don’t wire this. [a11y]
Never
- Put information the user must read or act on in a toast — it auto-dismisses and may be missed. Use Notice or Dialog. [a11y]
- Make a toast the only record of an outcome (e.g. the sole error explanation for a failed save).
- Mount more than one toaster, or mount it inside a repeating/re-rendering component.
- Stack multiple actions in one toast, or rely on the
actionas the only way to perform it. - Convey the toast’s meaning by color alone — the icon and wording must carry it. [a11y]
- Use
theme="system"expecting it to follow the app’s dark mode.
Related
- Notice component — persistent, inline messaging that stays on the surface.
- docs/patterns/copy.md — what the message says: short, outcome-first.
- docs/patterns/action-hierarchy.md — when an outcome deserves a toast vs a heavier surface.