Feedback
Toaster
Transient, announced notifications with a LUC skin on Sonner's behaviour.
Examples
Five tones
Title, optional description, optional action. The surface never changes shade; the rail and the icon do, which is what keeps a green wall out of a dense page.
toast("Deployment queued");
toast.success("Saved", { description: "3 rows updated" });
toast.warning("Stale quote window");
toast.error("Ingest failed", { description: "Provider returned 503" });
toast.info("Two jobs are waiting");Loading, promises and actions
A promise toast is the honest way to show a long task: it changes in place rather than stacking three toasts, and it keeps the same element — so the live region announces one message, not three.
toast.promise(runStudy(), {
loading: "Running the study…",
success: "Study finished · report written",
error: "Study failed",
});
toast("Deleted 4 runs", { action: { label: "Undo", onClick: restore } });A toast that waits for the reader
A toast carrying an action deserves more than four seconds. `duration: Infinity` keeps it until the reader answers it, and hovering or focusing any toast pauses its timer — so a slow reader is never cut off mid-sentence.
toast("Two datasets are stale", {
description: "Refresh them from the pipeline page.",
duration: Infinity,
action: { label: "Refresh", onClick: refresh },
});A stack, not a queue of surprises
Several toasts collapse into a stack with the newest in front. The viewport shows four and the rest wait behind them, so a burst of six jobs cannot cover the page.
["BTC-USD", "ETH-USD", "SOL-USD"].forEach((symbol, index) =>
toast("Quote refreshed", { description: symbol, duration: 4000 + index * 600 }),
);Usage
import { Toaster, toast } from "@luc/ui/components/toast";<Toaster />
toast.success("Saved", { description: "3 rows updated" });`Toaster` is mounted once at the app root; the theme comes from `LucProvider`, so a toast follows an explicit theme choice rather than the operating system.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
Toaster | 20 documented props | ({ position, className, ...props }: ToasterComponentProps) => import("@types/react").JSX.Element |
Toaster props
Type: ToasterComponentProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
className? | string | undefined | — | Extra classes for the toast viewport. |
closeButton?inherited | boolean | undefined | — | |
containerAriaLabel?inherited | string | undefined | — | |
customAriaLabel?inherited | string | undefined | — | |
dir?inherited | "auto" | "ltr" | "rtl" | undefined | — | |
duration?inherited | number | undefined | — | |
expand?inherited | boolean | undefined | — | |
gap?inherited | number | undefined | — | |
hotkey?inherited | string[] | undefined | — | |
icons?inherited | ToastIcons | undefined | — | |
id?inherited | string | undefined | — | |
invert?inherited | boolean | undefined | — | |
mobileOffset?inherited | Offset | undefined | — | |
offset?inherited | Offset | undefined | — | |
position?inherited | Position | undefined | — | |
richColors?inherited | boolean | undefined | — | |
style?inherited | CSSProperties | undefined | — | |
swipeDirections?inherited | SwipeDirection[] | undefined | — | |
toastOptions?inherited | ToastOptions | undefined | — | |
visibleToasts?inherited | number | undefined | — |
Guidelines
Do
- Use a toast for something the reader just caused and does not need to answer twice.
- Use `toast.promise` for a long task: one surface that changes state beats three stacked toasts.
- Give an undoable action an action button in the toast rather than a confirmation dialog in front of it.
Don't
- Do not use a toast for an error the reader has to fix in a form; that message belongs beside the field.
- Do not stack more than a few at once — the viewport shows four and the rest queue behind them.
- Do not put a destructive confirmation in a toast: it disappears before a careful reader answers it.
Accessibility
Sonner owns the mechanics and the live region: a toast is announced politely, `closeButton` gives every one a named dismiss, and hover or focus pauses the timer so a slow reader is not cut off. The viewport never takes focus, so a toast cannot interrupt typing, and the LUC skin keeps the semantics it inherited — tone is carried by a rail and an icon as well as by colour, and the loading icon is `data-motion-essential`, so it becomes a slow pulse under reduced motion instead of freezing.
Where it appears, and where it is styled
`position`, `offset` and `visibleToasts` belong to the single `Toaster` you mount, not to an individual toast: the viewport is one surface, and moving it per message is how two toasts end up on top of each other. The visual layer is one file — it maps Sonner's documented custom properties to LUC tokens, then styles the parts variables cannot reach, under a `.luc-toaster` prefix.
Related