Recipes
Show a toast while a promise runs
Report the loading, success and failure of one async call with toast.promise.
View as Markdown for coding assistants, or see llms.txt.
Pass the promise to toast.promise with a message for each outcome. One toast moves from loading to success or error, so nothing stacks up.
"use client";
import { Button } from "@azmr/ui/button";
import { toast } from "@azmr/ui/toast";
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function ToastPromise() {
return (
<div className="flex flex-wrap gap-2">
<Button
variant="outline"
onClick={() =>
toast.promise(
wait(1500).then(() => 12),
{
loading: "Exporting…",
success: (rows) => `Exported ${rows} rows`,
error: "Export failed",
},
)
}
>
Export (succeeds)
</Button>
<Button
variant="outline"
onClick={() =>
toast
.promise(
wait(1500).then(() => Promise.reject(new Error("Disk full"))),
{
loading: "Exporting…",
success: "Exported",
error: (e: Error) => `Export failed: ${e.message}`,
},
)
.catch(() => {})
}
>
Export (fails)
</Button>
</div>
);
}The pattern
import { toast } from "@azmr/ui/toast";
toast.promise(save(), {
loading: "Saving…",
success: (rows) => `Saved ${rows} rows`,
error: (e: Error) => `Could not save: ${e.message}`,
});successanderrorcan be a string or a function of the resolved value or the error.- A rejected promise is still rejected for you:
toast.promisereturns the promise, so catch it (.catch(() => {})) if you have nothing more to do, as the failing button above does. - Toasts are announced by a polite live region and do not move focus, so the person keeps typing.
- Mount one
Toasternear the root of the app. Toasts show for a few seconds, so use one only for something that needs no decision. If the person has to choose, use a dialog.
For a toast with an action, such as Undo, see the confirm-delete example.