Photon Design System

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, Toasty } 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>
  );
}

Types

type picks the status ink — success, error, warning, info — and tints the hairline, the glyph, and the title together, with a breath of the same ink washed over the surface. info speaks in the brand ink.

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 },
});

Stacking

Toasts rest as a stack, Kumo’s math: each card behind rises 12px at −10% scale per depth, its words hidden behind the front card — at most three visible (the provider’s limit; the rest park until room frees). Hover or focus the stack and it expands into a true list with a 12px gap; swipe a card to dismiss it. New cards rise in from below and leave the same way.

Promise

manager.promise walks one card through the work: loading shows the spinner, then the card becomes the success or error outcome in place.

Anatomy

  • Toast.Provider — holds the toast queue; wrap it once near the root
  • Toast.useToastManager() — add() to enqueue, plus the toasts list
  • 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 (default 5000) — auto-dismiss delay in ms; 0 disables it
  • limit — number (default 3) — most toasts shown at once; older ones get data-limited
  • toastManager — ToastManager — an external manager from Toast.createToastManager()

useToastManager().add(options) — enqueue a toast

  • title — ReactNode — the heading, read by Toast.Title
  • description — ReactNode — the supporting line, read by Toast.Description
  • type — string — style hook / conditional-render key
  • priority — low · high (default low) — high announces assertively
  • timeout — number (default 5000) — per-toast override
  • id — string — reuse an id to update the toast in place and refresh its timer
  • actionProps — React.ComponentProps<'button'> — props for the Toast.Action button
  • 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 (from toasts)
  • swipeDirection — up · down · left · right or an array (default ['down', 'right']) — swipe-to-dismiss directions

Accessibility

  • Semantics — Toast.Viewport is a role="region" with aria-live="polite". Each Toast.Root is a role="dialog" (or alertdialog when priority: 'high') labelled by its Title and described by its Description; high-priority toasts are also announced through a hidden role="alert" region.
  • Icon-only buttons — Toast.Close is a <button> with no text, so it needs an aria-label (e.g. "Dismiss"). Toast.Action is a real button — give it a clear label like Undo.
  • Keyboard — pressing F6 moves 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.