Components · Feedback indicators

Toast notification

A short, transient confirmation that the system did the thing the user just asked for. Inherits the Banner's tone-tinted vocabulary, then adds the runtime layer the Banner doesn't need: a fixed-position queue, auto-dismiss with pause-on-hover, slide entrance, and a single inline action.

Documentedby Derek Fidler

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.

  1. 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.

  2. Title

    Inter Tight Medium 14 / 20 in text.on-fill.<tone>. One line preferred. Lead with the verb that already happened: Saved, Sent, Archived.

  3. Description (optional)

    Inter Tight Regular 13 / 18 at 85 % opacity. Cap at two lines — toasts are read in passing, not studied.

  4. Dismiss button

    28 × 28 close glyph. Always present unless the toast is itself the result of a critical interaction the user must acknowledge.

  5. 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: paused on 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 mouseenter and resumes on mouseleave with 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: reduce the 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 use role="alert" + aria-live="assertive" — they interrupt because the user needs to know now.
  • Keyboard: the toast is tabIndex=0 so the user can focus it and press Escape to dismiss. Tab moves 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 Escape dismisses, 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.

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()

PropTypeDefaultDescription
titleReactNodeThe 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).
descriptionReactNodeOptional second line at 85% opacity. Cap at two lines — past that, promote to a banner.
durationnumber5000Auto-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.
idstringOptional stable id. Reuse the same id to coalesce repeats — the existing toast updates in place instead of stacking.
showProgressbooleantrueToggle the 2 px progress line at the bottom. Auto-disabled when duration is 0.
showDismissbooleantrueToggle the trailing close button. Drop it only when the toast must be acknowledged via its action.

<Toaster />

PropTypeDefaultDescription
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.
maxVisiblenumber4Cap on visible toasts before the oldest is discarded. Past four the stack stops being a queue.
gapnumber8Pixel gap between stacked toasts.