Default — top-right, 5s auto-dismiss
Click the trigger. The toast slides in from the right, drains a 2 px progress bar over 5 seconds, then exits. Hover to pause; the bar pauses with it.
Overview
A toast is the system saying “done” in the corner of the screen. It announces a result the user already knows the cause of — they pressed Save, the toast confirms it landed. Toasts inherit the same five tones, fills, and icons as the Banner, so a settlement warning reads identically whether it lives in the page shell or rises out of the bottom-right. Where Banner is part of the page, Toast lives on top of it: floating, time-limited, and replaceable.
Toast confirms. Banner announces. Modal blocks.
If the user caused the event, use a toast. If the message is true for everyone on the surface (outage, policy change), use a banner. If the user must respond before continuing, use a modal. The three components share visual DNA on purpose, but the choice is about the user's relationship to the message — not about how loud you want it to feel.
Anatomy
A 380 px-wide tinted card with a leading icon, one or two lines of copy, an optional inline action, a dismiss button, and a 2 px progress line at the bottom that drains as the auto-dismiss timer ticks.
Order archived
Restore it within 30 days.
Tone icon
20 px Material outlined glyph in the tone's ink colour. Five tones, five icons — same set as Banner. Decorative only;
aria-hidden.Title
Inter Tight Medium 14 / 20 in
text.on-fill.<tone>. One line preferred. Lead with the verb that already happened: Saved, Sent, Archived.Description (optional)
Inter Tight Regular 13 / 18 at 85 % opacity. Cap at two lines — toasts are read in passing, not studied.
Dismiss button
28 × 28 close glyph. Always present unless the toast is itself the result of a critical interaction the user must acknowledge.
Progress bar
2 px line at the bottom edge in the tone's ink at 35 % opacity. Drains via a CSS animation so it pauses free with
animation-play-state: pausedon hover.
Tones
Five tones, same fills and icons as Banner. The priority on announce-to-screen-reader differs: success / caution / info are polite; warning / critical are assertive (they interrupt the screen reader to read the toast immediately).
tone="success"
Polite priority — announced when the user pauses.
tone="caution"
Polite priority — announced when the user pauses.
tone="warning"
Assertive priority — interrupts screen-reader flow.
tone="critical"
Assertive priority — interrupts screen-reader flow.
tone="info"
Polite priority — announced when the user pauses.
Content variants
Three content shapes cover every confirmation in the product. A toast that needs more shape than this isn't a toast — promote it to a banner or a modal.
Title only
The minimum. Use for confirmations the user already understands from context — Saved, Copied, Sent.
Title + description
A second line for context the user couldn't predict. Cap the description at two lines — toasts shouldn't be read like banners.
With action
A single inline action — Undo, Retry, View. The action dismisses the toast on click. Never two actions; that's a banner or a modal.
Positions
Six positions, anchored to the viewport. Pick one per surface and stick to it — moving the toast around between routes makes the user hunt for the confirmation each time. Default is top-right on desktop and bottom-center on mobile.
position="top-right"
position="top-center"
position="top-left"
position="bottom-right"
position="bottom-center"
position="bottom-left"
Duration
Three durations cover the cases. Hover pauses the timer; the progress bar pauses with it. A duration of 0 makes the toast sticky — it stays until the user dismisses it or activates the inline action.
3 s — quick confirm
8 s — needs reading
0 — sticky, requires dismissal
Stack
Multiple toasts stack with an 8 px gap. Newer toasts arrive at the position-anchored edge — top-positioned stacks grow downward, bottom-positioned stacks grow upward — so the newest message is always closest to the screen edge the eye is already watching.
Stack — newest on top
Click the trigger several times. Toasts gap by 8 px; older ones drop off the bottom of the queue once the newest pushes past four.
Behavior
- Auto-dismiss is the default. Toasts last 5 seconds unless overridden. Quick confirms (3 s) when the message is one word; long toasts (8 s) when the user needs to read; sticky (0 ms) only when the toast carries an action that must be taken.
- Hover pauses, leave resumes. The timer freezes on
mouseenterand resumes onmouseleavewith the remaining duration. Focus / blur do the same — keyboard users get the same grace. - One inline action, never two. The action sits below the description as an underlined text button. Activating it dismisses the toast. If you find yourself wanting two actions, the message belongs in a banner or a modal — toast is for confirmation, not deliberation.
- Newest stacks toward the edge. For top-positioned stacks, new toasts append at the top and older ones drop down. For bottom-positioned stacks, new toasts append at the bottom and older ones rise. The newest message is always closest to the screen edge.
- Cap the queue at four. Past four visible toasts the stack stops being a queue and starts being a list. Discard the oldest as new ones arrive, or coalesce repeats (“3 orders saved”) so the user reads one message instead of three.
- Reduced motion drops the slide. Under
prefers-reduced-motion: reducethe toast appears in place; the progress bar still drains so the user can still see how long they have. The auto-dismiss timer is independent of motion preference.
Accessibility
- Roles by tone: success, caution, and info use
role="status"+aria-live="polite"so the screen reader finishes its current sentence before announcing the toast. Warning and critical userole="alert"+aria-live="assertive"— they interrupt because the user needs to know now. - Keyboard: the toast is
tabIndex=0so the user can focus it and pressEscapeto dismiss.Tabmoves focus into the toast, then onto the inline action and the dismiss button in DOM order. - Focus does not leave the page.Toasts never steal focus on appear — that's the modal's job. Focus only lands on the toast if the user reaches for it.
- Don't carry meaning in colour alone. Every tone is paired with an icon (Check, WarningAmber, ErrorOutline, InfoOutlined) so colour-blind users still read the result. The title verb does the same job in words.
- Touch targets: the dismiss button is 28 × 28 px, below the 44 px touch-target floor — but the entire toast is also a focusable surface that
Escapedismisses, so reaching for the small button is never the only path.
Code
Mount one Toaster at the app root and call toast() from anywhere — server actions, event handlers, optimistic mutations. The function returns the toast id so the caller can dismiss it imperatively (e.g. when an in-flight request resolves to an error).
tsx
import { Toaster, toast } from "@flatpay-dk/ui";
// Mount once at the app root — usually inside the providers.tsx tree.
<Toaster position="top-right" />
// Then anywhere in the tree:
toast.success("Order saved");
toast.warning("Settlement delayed", {
description: "Your bank reported a temporary outage.",
});
toast.critical("Payment declined", {
description: "Click to retry — the toast won't auto-dismiss.",
duration: 0,
action: { label: "Retry", onClick: retryPayment },
});
// Imperative dismiss — useful when the cause finishes resolving
const id = toast.info("Saving…", { duration: 0 });
await save();
toast.dismiss(id);
// Coalesce repeats by passing the same id
toast.info("3 orders saved", { id: "save-batch" });
// In a server action, fire from the client side after revalidation
const handle = async () => {
const result = await saveOrder();
if (result.ok) toast.success("Order saved");
else toast.critical("Couldn't save", { description: result.error });
};Best practices
Toast misuse is the loudest in the system — a wrong toast interrupts every user on every page. The questions below catch the common ones.
Order saved
Funds will move on the next settlement run.
Do
Use a toast to confirm what the user just did. The verb already happened — Saved, Sent, Archived.
Scheduled maintenance Sunday 03:00 CET
Settlement may be delayed by up to two hours.
Don't
Don't use a toast for something the user didn't cause. System-wide outages and policy changes belong in a banner.
Order archived
Restore it within 30 days.
Do
Pair an action with a sticky toast when the user might want to undo. Eight seconds is plenty for a quick reversal.
Order archived
Restore it within 30 days, or delete permanently.
Don't
Don't pile two actions onto one toast. The user needs space to think, which a toast doesn't give them — promote to a modal.
3 orders saved
Do
Coalesce repeats. Three saves in a row become 'Three orders saved' — one toast, one read.
Order saved
Order saved
Order saved
Don't
Don't fire a separate toast for every save. Three identical toasts stacking is a sign the upstream code is too chatty.
Props
toast()
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | The lead message. Inter Tight Medium 14/20 in the tone's ink. Lead with the verb that already happened. |
| tone | "success" | "caution" | "warning" | "critical" | "info" | "info" | Tone preset. Drives the fill colour, icon, and screen-reader priority (polite vs assertive). |
| description | ReactNode | — | Optional second line at 85% opacity. Cap at two lines — past that, promote to a banner. |
| duration | number | 5000 | Auto-dismiss duration in milliseconds. Set to 0 for a sticky toast that requires explicit dismissal. |
| action | { label: string; onClick: () => void } | — | Optional inline action. The toast dismisses automatically when the action fires. |
| id | string | — | Optional stable id. Reuse the same id to coalesce repeats — the existing toast updates in place instead of stacking. |
| showProgress | boolean | true | Toggle the 2 px progress line at the bottom. Auto-disabled when duration is 0. |
| showDismiss | boolean | true | Toggle the trailing close button. Drop it only when the toast must be acknowledged via its action. |
<Toaster />
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "top-right" | "top-left" | "top-center" | "bottom-right" | "bottom-left" | "bottom-center" | "top-right" | Anchor for the stack. Pick one per app and stick to it — desktop default top-right, mobile default bottom-center. |
| maxVisible | number | 4 | Cap on visible toasts before the oldest is discarded. Past four the stack stops being a queue. |
| gap | number | 8 | Pixel gap between stacked toasts. |