Toasts
Platform rules for transient feedback, async status, undo actions, and custom notification cases.
Overview
Toasts are a core platform feedback pattern.
In this codebase, there are two layers:
| Layer | What it is | Who should use it |
|---|---|---|
Toast / DefaultToast | Low-level composable UI | Shared-ui maintainers and special custom cases |
showToast / showPromiseToast | Standard toast API | Almost all product code |
The default rule is simple:
Use showToast for ordinary transient feedback. Use showPromiseToast for async work that should move through a pending state and then resolve to success or error. Only build a custom toast when the standard toast layout is not enough.
Because toasts are used heavily across the brand dashboard, this pattern deserves its own page rather than being buried inside a generic feedback section.
Default Pattern
Use showToast for most success, error, info, or neutral notifications.
import { showToast } from '@fsai/shared-ui';
showToast({
type: 'success',
content: 'Workflow exported',
});This is the normal pattern used throughout the brand dashboard for:
- save success
- import/export feedback
- validation or mutation errors
- small completion messages
When To Use showToast
Use showToast when the feedback is:
- transient
- not blocking
- short enough to scan quickly
- not the only place critical information appears
- related to a user action or an important background event
Common good fits:
- "Saved"
- "Invite sent"
- "Workflow imported"
- "Failed to save changes"
- "Copied to clipboard"
Async Workflows
Use showPromiseToast when a single async action should show pending progress and then transition to success or error.
await showPromiseToast(saveChanges(), {
pending: {
type: 'neutral',
content: 'Saving changes...',
},
success: {
type: 'success',
content: 'Changes saved',
},
error: {
type: 'error',
content: 'Failed to save changes',
},
});This is the right fit for:
- report generation
- CSV import flows
- permission or bulk-update saves
- other user-triggered async work where a pending state is meaningful
Important behavior:
- the pending toast is delayed slightly so very fast operations do not flash
- the pending toast is kept visible for a minimum time so it does not flicker
- success or error updates reuse the same toast id rather than stacking new toasts
Undo And Action Toasts
Use showToast actions when the user may immediately reverse or follow up on the notification.
const toastId = showToast({
type: 'neutral',
title: 'Lead Deleted',
content: 'The lead has been deleted',
actions: [
{
text: 'Undo',
onClick: () => {
onUndo();
toast.dismiss(toastId);
},
},
],
});This is the pattern used in Leads.tsx.
Use action toasts for:
- undo
- retry
- open/view follow-up actions
Do not overload the toast with too many actions or turn it into a mini workflow.
Global Error Handling
The brand dashboard wires showToast into the app-level React Query caches in App.tsx.
That means many unhandled query and mutation errors already become platform toasts by default.
Important consequences:
- do not add a second local toast for the same error unless you intentionally want duplicate feedback
- use
meta.skipDefaultErrorHandlerwhen a query or mutation should not trigger the global default behavior - repeated global errors can share a
toastIdto dedupe notifications
This global behavior is one of the main reasons AI agents should prefer the standard toast helpers rather than rolling custom notification logic.
When Custom Toasts Fit
Use a custom toast only when the default showToast layout is not enough.
Good custom-toast cases:
- upload progress with expanding file lists
- custom live-updating notification UIs
- richer layouts that still fit inside toast-sized feedback
Representative example:
fsai/apps/brand-dashboard/src/context/UploadContext/UploadContext.tsxupdates a customUploadProgressToastwithtoast.update(...)
This is the exception, not the rule.
When Not To Use A Toast
Do not use a toast when the feedback needs to be:
- blocking
- persistent until resolved
- deeply instructional
- tied to a field-level validation issue
- visible as part of page content rather than transient feedback
Use another pattern instead:
| Need | Better pattern |
|---|---|
| Blocking confirmation or required decision | Modal |
| Field-level validation | inline FieldError |
| Persistent warning or prerequisite explanation | inline AlertBox |
| Rich promotional or workflow panel | dedicated component, not a toast |
DemoBookingToast.tsx in brand dashboard is a good reminder that not every bottom-right floating surface should be implemented as a platform toast.
App Wiring
Shared toasts depend on app-level setup:
- a
#toast-rootelement in the app HTML <ToastPortal />mounted near the app rootreact-toastify/dist/ReactToastify.cssimported once
In the brand dashboard this is already wired in index.html and App.tsx.
If you are working in another app, confirm this setup exists before expecting showToast to work.
Brand Dashboard Usage Patterns
Representative usages:
| Pattern | Path | Notes |
|---|---|---|
| Global default error toasts | fsai/apps/brand-dashboard/src/App.tsx | React Query query and mutation caches show standard error toasts. |
| Simple success/error feedback | fsai/apps/brand-dashboard/src/pages/Workflows/components/toolbar/WorkflowToolbar.tsx | Export, import, and validation feedback uses showToast. |
| Undo action toast | fsai/apps/brand-dashboard/src/pages/Leads/Leads.tsx | Neutral toast with an Undo action and manual dismiss. |
| Promise lifecycle toast | fsai/apps/brand-dashboard/src/pages/Analytics/components/AnalyticsReportsDialog/AnalyticsReportsDialog.tsx | Uses showPromiseToast for report generation/opening. |
| Import flow status | fsai/apps/brand-dashboard/src/modules/csv-ingestion/lead-import/LeadImportFlow/LeadImportFlowPage.tsx | Promise-driven async flow feedback. |
| Custom upload progress toast | fsai/apps/brand-dashboard/src/context/UploadContext/UploadContext.tsx | Uses raw react-toastify for a special progress UI. |
Important Gotchas
- Prefer the
showToastfunction, but be aware that some feature APIs also use a boolean prop namedshowToast. That boolean is not the shared helper. - The brand-dashboard
modules/toastfolder is about Toast POS integration, not UI notification toasts. - Raw
toast.error(...)fromreact-toastifyexists in some legacy areas such as upload flows, but it bypasses the sharedDefaultToaststyling. showToastreturns a toast id, which can be used withtoast.dismiss(id)for action flows like Undo.
Guidelines
- Do use
showToastas the default notification API - Do use
showPromiseToastfor async work with meaningful pending state - Do use toast actions sparingly for high-value follow-up actions like Undo
- Do rely on the global error handler when it already covers the failure path
- Don't create custom toast layouts unless the standard default toast is genuinely insufficient
- Don't use a toast for field validation, blocking decisions, or long-form instructional content
- Don't confuse UI toast notifications with Toast POS integration code