Toast
A transient message raised from anywhere, rendered somewhere else.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/toast.jsonUsage
Render the provider and the viewport once, near the root of the app:
import { Toaster, ToastProvider } from '@/components/ui/toast'
export default function RootLayout({ children }) {
return (
<ToastProvider>
{children}
<Toaster />
</ToastProvider>
)
}Then raise a toast from anywhere inside it:
import { useToast } from '@/components/ui/toast'
const toast = useToast()
toast.add({
type: 'success',
title: 'Report signed and sent',
description: 'ACC-4471902 is with the referring clinician.',
})Toasts are not rendered by the code that raises them. add() pushes an object into a store the
provider owns, and the Toaster renders whatever is in it. That indirection is the point: an
upload that finishes after the user has navigated away still has somewhere to say so, and no
component has to stay mounted to hold the message.
A toast is the least reliable place to put information. It is transient, it can be missed entirely, and a screen reader announces it once. Anything a clinician must act on belongs on the page as well — use an Alert or a field error, and treat the toast as the reminder rather than the record.
Composition
<ToastProvider> {/* owns the store; limit, timeout, toastManager */}
…your app…
<Toaster /> {/* portal + viewport + the stack */}
</ToastProvider>Toaster renders a role="region" labelled "Notifications" with a polite live region inside it,
all of which arrives with the primitive. Hovering or focusing the viewport expands the stack from
a peeked pile into a readable list and stops the timers, so a toast cannot expire while it is
being read.
Toast itself reads everything it shows off the object the manager handed it — title,
description, actionProps, type. Each part renders nothing at all when its field is absent,
so a bare add({ description }) does not leave an empty heading behind.
Tones
type is a free string in Base UI: it lands on the root as data-type and the stylesheet does
the rest. These four are the design system's statuses — the same four
Badge and Alert use — and each carries the
icon Figma's Status bar pairs with it, so a success reads the same in a bar and in a toast.
With an action
Pass actionProps — anything a <button> takes. Give the toast a fixed id and a second failure
updates the one already on screen and restarts its timer, rather than stacking three copies of the
same sentence:
toast.add({
id: 'prior-fetch',
type: 'urgent',
priority: 'high',
title: 'Failed to retrieve prior study',
actionProps: { children: 'Retry', onClick: fail },
})An action is also the honest response to priority: 'high'. If the message is urgent enough to
interrupt, it should usually offer the thing to do about it.
Async work
promise() shows the loading message with no timeout, then rewrites the same toast when the
promise settles:
toast.promise(work, {
loading: { title: 'Uploading series' },
success: result => ({ type: 'success', title: `${result.count} slices are in the archive.` }),
error: error => ({ type: 'urgent', title: 'Upload failed', description: error.message }),
})Doing it by hand gives you an "Uploading…" that expires while the upload is still running, and then a second toast appearing from nowhere to contradict it.
Stacking and sticky toasts
limit on the provider caps how many are on screen; timeout sets the default dwell. position
on the Toaster moves the pile — and moves the swipe direction with it, so a toast pinned to the
top cannot be dismissed by dragging it down into the page.
timeout: 0 opts a single toast out of dismissal entirely. Use it only where the message must be
acknowledged — an unassigned urgent study, not a saved draft.
Outside React
Some of the places worth raising a toast from are not components: an API client, a websocket handler, a route guard. Create a manager outside the tree and hand it to the provider:
import { createToastManager } from '@/components/ui/toast'
export const toast = createToastManager()<ToastProvider toastManager={toast}>toast.add(...) then works from anywhere, hook or not.
API Reference
ToastProvider
| Prop | Type | Default | Description |
|---|---|---|---|
limit | number | 3 | How many toasts are on screen at once. |
timeout | number | 5000 | Default dwell in milliseconds. 0 never dismisses. |
toastManager | ToastManager | — | A manager from createToastManager, for raising toasts outside React. |
Toaster
| Prop | Type | Default | Description |
|---|---|---|---|
position | "bottom-right" | "bottom-center" | "top-right" | "top-center" | "bottom-right" | Where the stack sits. Also sets the swipe direction. |
useToast
Returns the manager: add, update, close, promise, and the live toasts list.
add(options) takes title, description, type, id, timeout, priority, actionProps
and the rest of Base UI's toast options. See the
Base UI documentation for the full set.