Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Promise Toast Notification Block for React

An invoicing app's invoice with a Send button: one toast follows the request through the toaster's own promise call, Sending, then Invoice sent or Couldn't send with a Retry that succeeds, and a toggle group chooses how the next request ends.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Follow a request from the press to the answer in one toast, with this promise toast for React with Base UI. It's invoice 1042 of Tallybarn, an invoicing app, drawn in a hairline frame with a plus mark on each corner: a top bar with the Send invoice Shine Button, the invoice's number, customer and status word, and its lines in greys. Above the page a Toggle Group says how the next request ends, Succeed or Fail. Press Send and the block calls the toaster's own promise with a 1.4 second request: the toast, wearing the look of the catalog's Toast, shows "Sending invoice 1042" and then, in place, becomes "Invoice sent" or "Couldn't send" with a Retry; Retry sends again and succeeds. The failure never times out, since it carries an action. On load the toast of the previous invoice is drawn in place; Send raises a real one at the end of the screen. It's built from Toast, Toggle Group, Shine Button, Button and Guides, with CSS motion only. For saves, sends, syncs and any call that can take a moment and fail.

FAQ

Why is a toast already on the page, and what do I keep?

So you can see a toast without sending anything: on load the toast of the previous invoice, "Invoice sent", is drawn in place at the stage's foot end, using the catalog toast's own classes. Send invoice raises a real toast and the drawn one stays gone until a reload. In your app keep the toaster and the follow call, and drop the drawn copy: the "in-place" state, DrawnToast and the ol in the frame.

How does the toaster follow a promise?

The block makes its own manager with createToaster() (Base UI's createToastManager), renders <Toaster toaster={toaster} /> once, and calls toaster.promise(job, { loading: { title }, success: { title, description }, error: { title, description, actionProps: { children: "Retry", onClick }, timeout: 0 } }). One toast changes in place, and the promise it returns rejects when the request does, so the block catches it.The page's own status word listens to the same promise, so it can't disagree with the toast.

What is the request, and how do I wire it up?

A promise settled by a timer: window.setTimeout 1.4 seconds after the press, resolving or rejecting by the toggle, stored in a ref and cleared when the block leaves the page. Nothing is sent. In your app replace the timer with your request, fetch or a mutation, and keep the rest: the toast needs only a promise.

What does Retry do?

It takes the failed toast away and sends again with a request that succeeds, so the demo shows the way back: a new toast says "Sending invoice 1042" and then "Invoice sent", and focus returns to Send invoice. Only a failed request answers a Retry, once, because a toaster can deliver an action twice. In your app retry with the same request, and keep the failed state on the page for as long as the problem lasts.

Is it accessible?

The real toast lives in a live region that speaks each state as it arrives, and the failure carries a Retry button and stays until it is answered. The toggle group is named "Make the request" and works with the arrow keys; Send invoice is quiet while a request is out and says so if pressed. The status word, "Draft", "Sending", "Sent" or "Not sent", is text, never only color, and the error's icon is the toast's own. The drawn copy is a list named "Notifications".

What stays local?

Everything: the invoice, the status and the toast live in the block and reset on reload, and the timer is the only request. The invoice numbers and the customer are constants at the top of the file.

How does it look on a phone?

The two choices share a row at full width above the page, the invoice's number and status stay on one line, and the drawn toast takes the width of the page minus a margin at its foot. The real toast is the library's own: full width at the end of the screen.

  • Pro

    A file transfer app's folder with an Upload button: one toast follows the run, a spinner and 1 of 3 uploaded with a Cancel while it runs, then the same toast becomes 3 files uploaded with an Open folder, or Upload cancelled.

  • Pro

    A payments dashboard's charge page with a declined card: an error toast that never times out, a Retry that counts the attempts until the third turns it into Contact support, and the request id with a Copy button.

  • Pro

    The settings page of a form builder with its Save settings button: two success toasts are drawn on the page, and pressing Save raises a real toast through the block's own toaster, stacking up to three.

  • Pro

    A messaging app's thread with a message toast that carries a Reply action: it never times out, Reply takes it away and puts the sender in the composer with focus, and Receive a message raises the next of three, up to three at once.

  • Pro

    Connection Lost Notification

    Live and empty states

    A whiteboard that keeps working offline: an offline bar docked at the foot with Retry now and its attempt count, the list of edits waiting to send, troubleshooting tips, and Work offline to fold it to a pill.

  • Pro

    Reconnected Notification

    Live and empty states

    A co-editor's back online notice drawn at the top centre of a document: it says what you missed, opens a catch-up list grouped by type with counts, and folds after a ten-second drain that holds on hover and focus.