Skip to content

Notifications

Mount one toaster, then dispatch notifications from React components or ordinary modules.

import { CratisComponentsProvider } from '@cratis/components';
<CratisComponentsProvider toaster>
<Application />
</CratisComponentsProvider>
import { toast } from '@cratis/components/Notifications';
toast.success({
title: 'Saved',
description: 'Your changes were saved.',
});
  • toast(options)
  • toast.success(options)
  • toast.info(options)
  • toast.warn(options)
  • toast.error(options)
  • toast.secondary(options)
  • toast.contrast(options)
  • toast.update(id, updates)
  • toast.dismiss(id?)
  • toast.promise(promise, states)

Each call returns the toast’s id, which update and dismiss take. toast.dismiss() without an id removes every toast.

Useful ToastOptions beyond title and description:

OptionPurpose
severity'success', 'info', 'warn', 'error', 'secondary', or 'contrast'; the shorthand methods set it for you
durationMilliseconds before auto-dismiss, overriding the toaster’s timeout. 0 keeps the toast until it is dismissed.
dismissibleThe close control is shown unless this is false
loadingMarks the toast as in progress; a loading toast does not auto-dismiss. toast.promise sets it on the pending toast.
actionButton attributes for one action button in the toast
renderCustom body; see Custom body
idYour own id; showing a toast with an existing id replaces that toast
onDismiss / onTimeoutCalled when the toast is dismissed or times out

The queue is shared across loaded Components copies. setToastDispatch() installs an application-owned dispatch and returns a scoped restore callback.

toastCommandResult(result, options?) turns an Arc command result into the matching toast and returns true when the command succeeded:

import { toastCommandResult } from '@cratis/components/Notifications';
const result = await command.execute();
if (toastCommandResult(result, { successTitle: 'Author registered' })) {
close();
}
ResultToast
SuccessSuccess toast titled successTitle (default 'Success') with optional successDescription; skipped when showSuccess: false
Not authorizedWarning toast titled unauthorizedTitle (default 'Not authorized')
InvalidError toast titled validationTitle (default 'Validation failed') listing the validation messages
ExceptionsError toast titled exceptionTitle (default 'Something went wrong'); exception messages and stack traces are not shown

It needs a mounted toaster like any other toast. Log result.exceptionMessages yourself if you need them.

The default Toaster is optional. An application that wants full control over how notifications render — a different animation library, a non-portal placement, or integration with an existing app-wide notification center — can build its own surface on the same primitives Toaster itself uses:

  • subscribeToToasts(listener) subscribes to the shared queue and returns an unsubscribe callback. Call it inside a useSyncExternalStore/useEffect binding.
  • getToastSnapshot() returns the current, immutable array of queued ToastRecord values — the snapshot to read whenever the subscription notifies of a change.
  • ToastRecord is ToastOptions & { id: ToastId }, the exact shape stored in the queue; render each record’s title/description/render, honor dismissible, and call the imperative toast API (or a custom ToastDispatch) to dismiss it.
  • ToastDispatch is the interface implemented by whatever setToastDispatch() installs — implement it to redirect every toast(...) call to a different in-app system (or to a test double) instead of the built-in queue.

A minimal custom subscriber:

import { useSyncExternalStore } from 'react';
import { subscribeToToasts, getToastSnapshot } from '@cratis/components/Notifications';
const CustomToastRegion = () => {
const toasts = useSyncExternalStore(subscribeToToasts, getToastSnapshot);
return (
<div role='region' aria-label='Notifications'>
{toasts.map((toast) => (
<CustomToastFrame key={toast.id} toast={toast} />
))}
</div>
);
};
  • Error frames use role="alert"; other frames use role="status".
  • The notification region is labeled and polite.
  • Every dismissible frame retains a localized close control, including custom bodies.
  • Auto-dismiss pauses while the frame is hovered or contains keyboard focus.
  • Timeouts have a five-second accessibility floor.
toast.error({
render: <FailureDetails />,
dismissible: true,
});

Custom content replaces only the body. The frame, severity indicator, timeout, and dismiss control remain owned by Components.

PropPurpose
position'top-left', 'top-center', 'top-right' (default), 'bottom-left', 'bottom-center', or 'bottom-right'.
limitMaximum visible frames; the newest are shown. Defaults to 3.
timeoutDefault timeout in milliseconds. Defaults to 6000; values below 5000 are raised to 5000.
dismissAriaLabelAccessible name for close controls. Falls back to the CratisComponentsProvider’s messages.notifications.dismiss, then 'Dismiss'.
regionAriaLabelAccessible name for the notification region. Falls back to the provider’s messages.notifications.region, then 'Notifications'.
ptStable region, toast, icon, content, title, description, action, and close parts.

Configure dismissAriaLabel / regionAriaLabel once for the whole application through CratisComponentsProvider’s messages.notifications, or override either per <Toaster> instance.

To pass these props through the provider, give toaster an object instead of true, for example <CratisComponentsProvider toaster={{ position: 'bottom-right', limit: 5 }}>.