Toast

Dictionary-driven notifications built on Sonner

Installation

pnpm add @wandercom/design-system-web

Integration

Add the Toaster component to your application root layout or provider:

10 lines
import { Toaster } from '@wandercom/design-system-web/ui/toast';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Toaster />
    </>
  );
}

Toasts remain interactive above modal layers. Touch, pointer, click, and focus events are consumed within the toaster, so dismissing a notification or using an action does not activate controls or dismiss overlays underneath.

Usage

Import the toast object to trigger toasts. The label and description props accept both dictionary keys and direct strings:

15 lines
import { toast } from '@wandercom/design-system-web/ui/toast';

export function Example() {
  return (
    <>
      <button onClick={() => toast.error({ label: 'ErrorGeneral', description: 'ErrorGeneral' })}>
        Show error toast
      </button>

      <button onClick={() => toast.success({ label: 'Success!', description: 'Your changes have been saved.' })}>
        Show success toast
      </button>
    </>
  );
}

Examples

Loading example...

Dictionary keys vs direct strings

The label and description props accept both dictionary keys and direct strings. When a key exists in the dictionary, the resolved copy is displayed. Otherwise, the string is used as-is.

17 lines
// Dictionary keys (resolved from the toast dictionary)
toast.success({
  label: 'CopiedToClipboard',
  args: { value: 'API key' },
});

// Direct strings
toast.success({
  label: 'Copied to clipboard!',
  description: 'Your API key has been copied.',
});

// Mixing both
toast.error({
  label: 'ErrorGeneral',
  description: 'Please try again in a few moments.',
});

With actions

Add action buttons to toasts for user interaction. When dismissible is true (the default), clicking an action also dismisses the toast.

18 lines
toast.default({
  label: 'ErrorActionFailed',
  description: 'ErrorActionFailed',
  actions: [
    {
      label: 'Try again',
      onClick: () => {
        console.log('Clicked try again');
      },
    },
    {
      label: 'Dismiss',
      onClick: () => {
        console.log('Dismissed');
      },
    },
  ],
});

Inline layout

Set inline to render the label and description side-by-side instead of stacked:

5 lines
toast.info({
  label: 'Theme',
  description: 'System preference',
  inline: true,
});

Duration and dismissible

Control how long toasts are displayed and whether they can be dismissed:

10 lines
toast.success({
  label: 'CopiedToClipboard',
  duration: 3000,
});

toast.info({
  label: 'ChangedTheme',
  args: { value: 'system' },
  dismissible: false,
});

Promise toast

Track promise-based operations with automatic loading, success, and error states:

8 lines
toast.promise<{ name: string }>(
  () => fetch('/api/booking').then((r) => r.json()),
  {
    loading: 'Processing...',
    success: (data) => `${data.name} confirmed successfully`,
    error: 'Failed to process booking',
  },
);

Each phase (loading, success, error) accepts a dictionary key, a direct string, or a full props object:

11 lines
toast.promise<{ name: string }>(
  () => fetch('/api/booking').then((r) => r.json()),
  {
    loading: { label: 'Processing...', description: 'Please wait' },
    success: (data) => ({
      label: 'Booking confirmed',
      description: `${data.name} is all set`,
    }),
    error: { label: 'ErrorGeneral', description: 'ErrorGeneral' },
  },
);

The promise method shows a loading toast while the promise is pending, then automatically transitions to a success or error toast based on the result. The success and error options also accept a callback that receives the resolved data or error, respectively. The returned promise can be chained further.

Custom dictionaries

Use createToast to build a toast instance backed by a custom dictionary. This keeps copy centralized and type-safe across your application.

14 lines
import { createToast } from '@wandercom/design-system-web/ui/toast';
import { createToastDictionary } from '@wandercom/design-system-shared/dictionaries/toasts';

const APP_TOASTS = createToastDictionary({
  BookingConfirmed: {
    label: 'Booking confirmed',
    description: ({ value }) => `Welcome, ${value}!`,
  },
} as const);

export const appToast = createToast(APP_TOASTS);

// Usage -- dictionary keys are fully typed
appToast.success({ label: 'BookingConfirmed', args: { value: 'John' } });

The shared dictionary keys (ErrorGeneral, CopiedToClipboard, etc.) are automatically included via createToastDictionary.

Toast methods

toast.default()

Notification without a preset icon. Accepts a custom icon prop.

toast.success()

Success notification with a green checkmark icon.

toast.error()

Error notification with a red exclamation icon.

toast.info()

Informational notification with an info icon.

toast.loading()

Loading notification with an animated spinner.

toast.promise()

Promise-based notification that transitions through loading, success, and error states automatically.

Props

All toast methods accept the following props (except toast.promise(), documented above):

label?:

string
Primary message text. Resolved from the dictionary if a matching key exists, otherwise used directly.

description?:

string
Secondary text displayed below the label. Resolved from the dictionary if a matching key exists, otherwise used directly.

args?:

ToastArgs
Dynamic arguments for interpolating dictionary messages. Supports count (number), date (Date), and value (string | boolean | number).

actions?:

{ label: string; onClick: () => void }[]
Action buttons displayed below the message. Clicking an action also dismisses the toast when dismissible is true.

icon?:

ReactElement
Custom icon element. Only available on toast.default() since other methods provide their own icon.

inline?:

boolean
Renders label and description side-by-side instead of stacked. Defaults to false.

dismissible?:

boolean
Whether the toast shows a close button and can be manually dismissed. Defaults to true.

duration?:

number
How long the toast is displayed in milliseconds.

Toaster props

The Toaster component accepts all Sonner ToasterProps. Defaults are applied for position (bottom-right) and toast width styling.

theme?:

'light' | 'dark' | 'system'
Theme for the toaster. Defaults to 'system'.

position?:

'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
Position of the toaster on screen. Defaults to 'bottom-right'.

Accessibility

Toast notifications are rendered by Sonner, which manages an aria-live region so screen readers announce new toasts automatically.

  • Each toast is rendered inside an accessible container with appropriate aria-live politeness
  • Dismissible toasts include a close button reachable via keyboard
  • Action buttons within toasts are focusable and keyboard-operable
  • Toasts do not trap focus, allowing users to continue interacting with the page
Toast