Toast
A transient, non-blocking notification — confirmation of a background action, a
brief status update. Built on Base UI Toast, which is driven imperatively
through a manager. For a choice the user must make, use
Dialog.
Import
import { Toast } from "@photon-ai/pho-ui/components/toast";
Basic
Wrap the tree in Toast.Provider, render a Toast.Viewport that maps
useToastManager().toasts to Toast.Roots, then call add() to show one.
function ToastList() {
const { toasts } = Toast.useToastManager();
return toasts.map((toast) => (
<Toast.Root key={toast.id} toast={toast}>
<Toast.Content>
<Toast.Title />
<Toast.Description />
</Toast.Content>
<Toast.Close aria-label="Dismiss">
<X />
</Toast.Close>
</Toast.Root>
));
}
function App() {
const toast = Toast.useToastManager();
return (
<Toast.Provider>
<Button onClick={() => toast.add({ title: "Deployment ready" })}>
Deploy
</Button>
<Toast.Portal>
<Toast.Viewport>
<ToastList />
</Toast.Viewport>
</Toast.Portal>
</Toast.Provider>
);
}
Action
Pass actionProps when you enqueue a toast and render a Toast.Action that
spreads them — one undo/retry affordance whose click also dismisses the toast.
Keep it to a single action; anything more is a dialog.
// In the list:
<Toast.Content>
<Toast.Title />
<Toast.Description />
{toast.actionProps ? <Toast.Action {...toast.actionProps} /> : null}
</Toast.Content>;
// Firing one:
manager.add({
title: "Project deleted",
actionProps: { children: "Undo", onClick: restore },
});
Anatomy
- Toast.Provider — holds the toast queue; wrap it once near the root
- Toast.useToastManager() —
add()to enqueue, plus thetoastslist - Toast.Portal → Viewport — where toasts stack (fixed, bottom-right)
- Toast.Root → Content → Title / Description / Close / Action — one toast
Props
Every rendered part also accepts className and render (Base UI’s prop for
swapping the underlying element).
Toast.Provider
- timeout —
number(default5000) — auto-dismiss delay in ms;0disables it - limit —
number(default3) — most toasts shown at once; older ones getdata-limited - toastManager —
ToastManager— an external manager fromToast.createToastManager()
useToastManager().add(options) — enqueue a toast
- title —
ReactNode— the heading, read byToast.Title - description —
ReactNode— the supporting line, read byToast.Description - type —
string— style hook / conditional-render key - priority —
low·high(defaultlow) —highannounces assertively - timeout —
number(default5000) — per-toast override - id —
string— reuse an id to update the toast in place and refresh its timer - actionProps —
React.ComponentProps<'button'>— props for theToast.Actionbutton - data —
object— custom data read back in the render (e.g. a color) - onClose / onRemove —
() => void— fired on dismiss / after the exit animation
The manager also returns close(id?), update(id, options), and
promise(promise, { loading, success, error }).
Toast.Root (<div role="dialog">)
- toast —
ToastObject(required) — the toast to render (fromtoasts) - swipeDirection —
up·down·left·rightor an array (default['down', 'right']) — swipe-to-dismiss directions
Accessibility
- Semantics —
Toast.Viewportis arole="region"witharia-live="polite". EachToast.Rootis arole="dialog"(oralertdialogwhenpriority: 'high') labelled by itsTitleand described by itsDescription; high-priority toasts are also announced through a hiddenrole="alert"region. - Icon-only buttons —
Toast.Closeis a<button>with no text, so it needs anaria-label(e.g."Dismiss").Toast.Actionis a real button — give it a clear label likeUndo. - Keyboard — pressing
F6moves focus to the toast region so keyboard users can reach the action and close buttons without hunting. - Timers pause — the auto-dismiss countdown pauses while the pointer is over the viewport, while a toast has focus, or while the window is blurred, so a toast can’t vanish mid-read.
Best practices
- Use a toast to confirm something already happened (“Saved”, “Deploy queued”), not to ask a question — anything requiring a decision belongs in a dialog.
- Keep the text to a title and one line; put any follow-up in a single
Toast.Action. - Don’t stack many at once or auto-dismiss critical errors too fast; let users read and dismiss them.