Toast

A short note that something just happened — a booking created, a change saved, a save that failed. Toasts slide in at the top and never take focus. A plain note leaves on its own; one with an action or an error stays until it's dismissed. Okami's Toaster over the vendored sonner one, with sonner's toast beside it.

Playground

Toast

Success & error

The two outcomes of nearly every action, each marked with a thin Hugeicons line, never filled: an ink check for success, a cross in the palette's one red for an error. A description, set small, adds the specifics — which booking, what to do next.

Loading

For work that takes a moment, toast.promise shows a spinner, then turns into the outcome in place — one toast, not three. To drive it by hand, keep the id from toast.loading and pass it to the next call.

With an action

An action, the black primary button, offers the one obvious next step. Undo is the best of them: it lets a reversible action skip its confirmation dialog. A toast with an action stays until the action is taken or the toast is closed with its X: nobody should lose an Undo to a timer.

Mounting

Mount the Toaster once, in the root layout, inside the intl provider. Feature code then only imports toast — from this module, never from sonner directly.

// app/[locale]/layout.tsx
import { Toaster } from "@/components/shared/toast";

<NextIntlClientProvider>
  {children}
  <Toaster />
</NextIntlClientProvider>

API reference

toast

PropTypeDefaultDescription
toast(title, options?)string | number—A plain message, no mark. Returns the toast's id; so do the calls below.
toast.success / .error(title, options?)—The outcomes: an ink check, or a red cross. An error stays until it's dismissed, so it can be read.
toast.warning / .info(title, options?)—A heads-up (an ink alert) or a neutral note (a quiet info mark).
toast.loading(title, options?)—A spinner that stays until the same id is updated or dismissed.
toast.promise(promise, { loading, success, error })—Loading, then the outcome, in one toast.
toast.dismiss(id?)—Closes one toast, or all of them.

Options

PropTypeDefaultDescription
descriptionReactNode—A quiet second line: which booking, what next.
action{ label, onClick }—One next step, as the black primary button. Pressing it closes the toast.
cancel{ label, onClick }—A ghost pill before the action. Rarely needed.
idstring | number—Updates the toast with this id in place instead of adding one.
durationnumber4000Milliseconds on screen, paused while hovered or the tab is hidden. With an action, or for an error, it's Infinity (and the X shows) unless the call sets one.

Toaster

PropTypeDefaultDescription
position"top-center" | "bottom-right" | …"top-center"Where toasts appear.
expandbooleantrueToasts list one under another rather than stacking into a pile.
visibleToastsnumber3How many show at once; the rest wait their turn.
closeButtonbooleanfalseA small ghost X on every toast.
idstring—Scopes a second Toaster; only toasts with the same toasterId land on it.

Theme is fixed to light and every part is drawn by Okami; the rest of sonner's Toaster props pass through.

Guidelines

  • A toast reports; it never asks. Anything that needs a decision is a Dialog, and anything people must not miss stays on the page.
  • Say what happened, to what, in the past tense: “Booking created”, “Changes saved”. An error says what failed and, in the description, what to do next.
  • One toast per event. Update a loading toast in place with toast.promise or its id instead of adding another.
  • For a reversible action, skip the confirmation dialog and offer Undo in the toast.
  • Anything the reader has to act on or read stays: a toast with an action, or an error, waits until it's dismissed (WCAG 2.2.1). The toast module does this; don't shorten it with a duration.
  • Don't toast what the page already shows — a saved field that visibly changed needs no second announcement.