Toast
A short message at the edge of the screen about what the crew just did, which leaves by itself or when dismissed.
On this page
Code
import { Button } from "@bosunkit/components/button";
import { ToastProvider, useToast, type ToastApi } from "@bosunkit/components/toast";
import { Download, Share2, SquareArrowOutUpRight, Undo2 } from "lucide-react";
import type { ReactElement } from "react";
export const description: string =
"Short messages about what the crew just did. A saved record leaves by itself, a deleted entry offers Undo and " +
"counts down the seconds it can still be pressed, a saved route offers three actions from one split button, an " +
"export shows a spinner until it finishes, and a failed sync stays until it is dismissed.";
function Buttons(): ReactElement {
const toast: ToastApi = useToast();
return (
<div style={{ display: "flex", flexWrap: "wrap", gap: "var(--space-md)" }}>
<Button
onClick={(): void => {
toast.show({
kind: "success",
title: "Record saved",
description: "Impeller service is marked complete.",
});
}}
>
Save record
</Button>
<Button
variant="ghost"
onClick={(): void => {
toast.show({
title: "Log entry deleted",
countdown: true,
actions: [
{
label: "Undo",
icon: Undo2,
onAction: (): void => {
toast.show({ kind: "success", title: "Entry restored" });
},
},
],
});
}}
>
Delete entry
</Button>
<Button
variant="ghost"
onClick={(): void => {
toast.show({
kind: "success",
title: "Route saved",
href: "#props",
description: "Falmouth to the Isles of Scilly.",
actions: [
{ label: "Open", icon: SquareArrowOutUpRight, onAction: (): void => undefined },
{ label: "Share with crew", icon: Share2, onAction: (): void => undefined },
{ label: "Export as GPX", icon: Download, onAction: (): void => undefined },
],
});
}}
>
Save route
</Button>
<Button
variant="ghost"
onClick={(): void => {
let finish: ReturnType<typeof setTimeout> | undefined;
let cancel: (error: Error) => void = (): void => undefined;
const exported: Promise<string> = new Promise<string>(
(resolve: (file: string) => void, reject: (error: Error) => void): void => {
cancel = reject;
finish = setTimeout((): void => {
resolve("falmouth-scilly.gpx");
}, 3000);
},
);
toast
.track(exported, {
running: {
title: "Exporting the route as GPX",
actions: [
{
label: "Cancel",
onAction: (): void => {
clearTimeout(finish);
cancel(new Error("cancelled"));
},
},
],
},
done: (file: string) => ({ kind: "success", title: "Route exported", description: file }),
failed: { kind: "error", title: "Could not export the route" },
})
.catch((): void => undefined);
}}
>
Export route
</Button>
<Button
variant="ghost"
onClick={(): void => {
toast.show({
kind: "error",
key: "sync-failed",
title: "Could not sync the log",
description: "The shore link is down. The entries stay on this device until it is back, and Bosun tries again every minute.",
footer: "Shore link, down since 13:12 UTC",
});
}}
>
Fail a sync (repeat it)
</Button>
<Button
variant="ghost"
onClick={(): void => {
toast.show({
kind: "attention",
persistent: true,
title: "Check the bilge",
description: "Stays until you dismiss it.",
});
}}
>
Keep until dismissed
</Button>
</div>
);
}
export default function Showcase(): ReactElement {
return (
<ToastProvider viewportLabel="Notifications" dismissLabel="Dismiss" moreActionsLabel="More actions">
<Buttons />
</ToastProvider>
);
}
A toast confirms an action in a few words: "Record saved", "Log entry deleted", with one more line and the actions the
crew can take about it. ToastProvider owns the stack and the region it is announced in; useToast() returns show
and dismiss. The newest toast stands nearest the edge the stack is anchored to, at the bottom for a stack at the
bottom, and rises there while it fades in as the older ones glide away from the edge to make room. Every toast shows;
a stack taller than the screen scrolls. A toast leaves by itself, by a swipe to the right, or by its dismiss button. As
it leaves, a toast at the end of the screen slides out past the screen's edge while it fades, one in the middle shrinks
a little as it fades, and the toasts that stay glide into its place in the same motion. Under reduced motion a toast
only fades and the others take their places at once. It is built on Base UI's toast, so its timer pauses while the
pointer or focus is on it and starts again when they leave, and F6 moves focus into the region. A toast takes the height
its text needs, so a short confirmation and a long explanation stack without overlapping.
actions lists what the crew can do about the message, most likely first, each with a label and an optional icon
drawn before it (Undo takes Lucide's Undo2). The first is the toast's button. Any others open from the menu of a
split button around it, named by the provider's moreActionsLabel, so the toast keeps one row of controls however many
actions it has; the menu opens above the toast stack, and the toast holds its timer while the menu is open. Every action
is a touch target as tall as the dismiss button, and the toast closes after any of them runs.
track(task, { running, done, failed }) shows a toast while a task runs, with a spinner as its mark, which turns
in place into done or failed when the task settles; the result's timer starts then. An action on the running toast
that ends the task, such as Cancel, closes it and its result is not shown. A running toast the crew dismissed comes
back with the result, since a finished or failed task is news.
href makes the title a link to what the message is about, such as the saved route or the restored entry; a path
inside the application goes through the provider's link component, and following it closes the toast.
footer adds one line under a rule at the toast's foot, in a card footer's small dim type: where the message comes
from and since when ("Shore link, down since 13:12 UTC"), with a time's reference written out. It lines up with the
title and the description.
countdown shows how long a toast has left: the seconds after its action's label and a bar along its foot that shortens
each second. It is for an action that can be pressed only for a while, such as Undo after a deletion, and it stops
whenever the toast's timer pauses. A toast with a countdown leaves when its time runs out, whatever its kind.
onClose runs once when a toast leaves without one of its actions run, by its timer, its dismiss button or a swipe, so
a deletion that offers Undo is made final there, at the moment Undo leaves the screen.
persistent keeps a toast until the crew press its dismiss button, whatever its kind and timeout. A toast that waits
for the crew this way, like an error, an attention message or a running task, keeps its place however many toasts
arrive after it. With a limit on the provider, the toasts that leave by themselves take the places left, newest
first, and the older ones wait out of sight with their time held until a place opens. A message that can
repeat takes a key ("sync-failed"): showing it again while it is on the screen updates that toast in place, restarts its
timer and counts the repeat, so a copy of it is never stacked. The count is a badge after the title ("×3") that grows and
fades in once on each repeat, and the toast stacks one edge under itself for its second showing and two from its third,
so a message that keeps coming reads as a small pile.
The kinds are info, success, error, attention and discovery, each with its own mark so a kind is never told by
color alone. There is no warning kind: that word is an alert's priority, and a toast is not an alert. An error and an
attention message stay until the crew dismiss them; the others leave after a few seconds. The caller translates every
word, so the component adds no catalog keys.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| childrenrequired | ReactNode | – | The page; anything inside it can call useToast. |
| viewportLabelrequired | string | – | The name of the region the toasts are in for a screen reader ("Notifications"); translated by the caller. |
| dismissLabelrequired | string | – | The name of each toast's dismiss button ("Dismiss"); translated by the caller. |
| moreActionsLabelrequired | string | – | The name of the button that opens a toast's further actions ("More actions"); translated by the caller. |
| placement | ToastPlacement | undefined | "bottom-end" | Where the toasts stack; the bottom end of the screen by default. |
| limit | number | undefined | Number.POSITIVE_INFINITY | How many toasts show at once; every one by default, the stack scrolling when it is taller than the screen. With a limit, a toast that waits for the crew (one that stays until dismissed, an error or attention message, a running task) still always shows; the toasts that leave by themselves take the places left, newest first and at least one, and the older ones wait out of sight with their time held. |
| defaultTimeout | number | undefined | 6000 | How long a toast of a kind that leaves on its own stays by default, in milliseconds. 6,000 by default. |
| motion | boolean | undefined | true | Whether a toast slides and fades in and out; on by default. Under reduced motion it only fades, whatever this says. |
Log entry deleted
Anchor alarm is off. (disappears in 6 s)
Related
- Task dialog: the dialog for a decision the crew must make before going on.
- Popover: a panel that stays open until it is closed.
- Alert card: the alert, which has a priority, flashes until acknowledged and stays.