Skip to content
Twilight React Playground
ثيم رائدaren

toast

functionBeginnerbrowserlive demo

Shows a short notification (success, error, warning, info, loading, or a promise's progress) that closes on its own.

import { toast, ToastFn, ToastOptions, ToastPromiseOptions, ToastUpdateOptions, ToastType } from '@salla.sa/twilight-theme-engine/components/toast';

In plain words

A toast is the small message that slides in at the edge of the screen, like "Added to your wishlist", and goes away after a few seconds. Call toast.success('Added to your wishlist') from a click handler and it appears.

There is one method per kind of message: success, error, warning, info and loading, plus toast('text') for a plain one. Each call returns an id. Pass it back to replace that toast (toast.success('Saved', { id })) or to close it (toast.dismiss(id)).

You never put the toast area on the page yourself: TwilightProvider, the component every theme wraps its pages in, already renders it.

Signature

const toast: ToastFn

interface ToastFn {
  (title: string, options?: ToastOptions): string | number;
  success / error / warning / info / loading:
    (title: string, options?: ToastOptions) => string | number;
  promise: <T>(promise: Promise<T>, options: ToastPromiseOptions<T>) => Promise<T>;
  dismiss: (id?: string | number) => void;
  update: (id: string | number, options: ToastUpdateOptions) => void; // broken: see gotchas
}

// sonner's per-toast options, with description narrowed to a string
interface ToastOptions extends ExternalToast {
  description?: string;
  // id, duration, position, closeButton, dismissible, action, cancel,
  // icon, onDismiss, onAutoClose, className, style…
}

interface ToastPromiseOptions<T = unknown> {
  loading: string;
  success: string | ((data: T) => string);
  error: string | ((error: Error) => string);
}

interface ToastUpdateOptions extends ToastOptions {
  title?: string;
  type?: ToastType;
}

type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading';

Try it live

toast() and its variants, sent to the Toaster the engine already renders on this page.Try this: set the type to loading and show it twice: it never closes on its own. Then compare the two "loading, then" buttons.
Storefront canvas · en · LTR
Controls
An optional second line.
The default is 4000. A loading toast ignores it.
Close button
What a theme writes
import { toast } from '@salla.sa/twilight-theme-engine/components/toast';

export function SaveButton() {
  return (
    <button type="button" className="btn btn--primary" onClick={() => toast.success('Added to your wishlist')}>
      Save
    </button>
  );
}

Example

app/components/account/SaveAddressButton.tsx
import { toast } from '@salla.sa/twilight-theme-engine/components/toast';

export function SaveAddressButton({ save }: { save: () => Promise<void> }) {
  const onClick = async () => {
    const id = toast.loading('Saving your address…');
    try {
      await save();
      toast.success('Address saved', { id }); // same id: replaces the loading toast
    } catch {
      toast.error('Could not save the address', { id, description: 'Please try again.' });
    }
  };

  return (
    <button type="button" className="btn btn--primary" onClick={onClick}>
      Save
    </button>
  );
}

How it behaves

  • Every method forwards to the method of the same name in sonner 2, the toast library the engine depends on, and returns the toast id: a number, unless you pass your own id.

  • Passing the id of a toast that is still on screen replaces it in place (type, title and options) instead of adding a new one. That is how a loading toast becomes a result.

  • A toast stays 4 seconds unless you pass duration (sonner's default; the engine's Toaster sets none). A loading toast never closes on its own. At most 3 are visible at once; the rest stack behind and spread out on hover.

  • promise(p, { loading, success, error }) shows loading until p settles, then success (a string, or a function of the value) or error. It returns your promise, not sonner's, so await it for the value and handle its rejection as usual. Only those three options are forwarded.

  • dismiss() with no id closes every toast.

  • action and cancel take { label, onClick } and render buttons inside the toast. Titles and descriptions are strings: the engine does not expose sonner's JSX toasts.

  • It is a plain object, not tied to React rendering: call it from any event handler, effect or function that runs in the browser. Toasts appear only where a Toaster is mounted, which TwilightProvider does unless you pass toast={false} (see Toaster).

Gotchas

  • toast.update(id, { type: 'success', title: 'Saved' }) does not update the toast. It calls sonner.success(id, { description: title }), which creates a second toast whose title is the id ("1") with your title under it, and the first toast stays (a loading one, forever). Adding id to the options only renames the old toast to its id (packages/theme-engine/src/components/toast/useToast.ts, update). Replace a toast with the variant and the id instead: toast.success('Saved', { id }).

  • Calling toast.success(…) in the body of a component shows a new toast every time that component renders. Call it from an event handler or an effect.

Related

Source and docs