Error Handling
Platform rules for blocked error surfaces, recovery actions, and choosing between ErrorState, ErrorGuard, panel, and DataTable fallbacks.
Overview
Error handling in the platform should help the user recover without losing context.
The most important rule is to choose the error surface that matches the scope of the failure:
| Use this | When it fits |
|---|---|
ErrorState | Static or feature-owned error surfaces such as full-page fallbacks or known 404 screens |
ErrorGuard | A region has a runtime error object and should map it to shared presets and recovery UI |
Panel.ErrorGuard | Panel content failed and the user should recover inside the panel flow |
DataTable errorState | A collection view failed and should preserve toolbar and table context |
AlertBox | The screen is still usable and only needs inline persistent error or warning messaging |
showToast | A user-triggered action failed and the UI should report it without replacing the current surface |
Do not use the same pattern for every failure. A blocked page, a failed table query, an inline validation problem, and a failed mutation after a button click are not the same UX problem.
Raw ErrorState Vs ErrorGuard
Use raw ErrorState when the feature already knows:
- the exact copy
- the icon
- the action
- the layout
This is common for:
- route-level fatal error pages
- explicit not-found pages
- shell-level failures with one obvious recovery path
Use ErrorGuard when the feature has a real error object and wants shared behavior:
- preset mapping for
authRequired,notFound,permissionDenied,rateLimited,serverError, andvalidation - shared default copy
- automatic message handling for non-server presets
- local overrides for title, description, and actions
Standard Recovery Patterns
Route Or Shell Failure
Use raw ErrorState for route-level or shell-level failures:
<ErrorState className="h-full min-h-full justify-center px-6 py-12">
<ErrorState.Icon />
<ErrorState.Title>Something went wrong</ErrorState.Title>
<ErrorState.Description>
This error has been reported to our team.
</ErrorState.Description>
<ErrorState.Actions>
<ErrorState.Action
role="button"
variant="primary"
onClick={() => window.location.reload()}
>
Reload Site
</ErrorState.Action>
</ErrorState.Actions>
</ErrorState>This is the pattern used in ErrorPage.tsx.
Data Region Failure
Use ErrorGuard for a failed content region and override serverError when retry is the right recovery path:
<ErrorGuard
error={error}
className="justify-center px-6 py-12"
serverError={{
title: 'Failed to Load Articles',
description:
'There was a problem loading published helpdesk articles. Please try again.',
primaryAction: {
label: 'Retry',
role: 'button',
variant: 'primary',
onClick: () => void refetch(),
leftAdornment: 'ArrowRotateClockwise',
},
}}
>
{children}
</ErrorGuard>This is the standard region-level retry pattern used in list, grid, and modal-body content.
Panel Failure
Panel failures should usually keep the user in the panel workflow and offer navigation recovery, not a generic reload-first message.
Use Panel.ErrorGuard or a wrapper like PanelStackErrorGuard when:
- the panel is already the user’s current context
- the previous panel in the stack is still meaningful
- closing the panel is a valid fallback
Common actions:
Backto the previous panelClose Panel- occasionally a feature-owned retry when the panel supports it
DataTable Failure
DataTable.Content preserves toolbar and content chrome while rendering failures through ErrorGuard.
Use:
errorfor the actual failure objecterrorStatefor overrides like retry copy and actionsemptyStateonly for true empty datasets
Example:
<DataTable.Content
view="table"
columns={columns}
error={error}
errorState={{
serverError: {
title: 'Failed to Load Locations',
description:
'There was a problem loading locations. Please try again.',
primaryAction: {
label: 'Retry',
role: 'button',
variant: 'primary',
onClick: () => void refetch(),
leftAdornment: 'ArrowRotateClockwise',
},
},
}}
emptyState={emptyState}
/>This keeps the collection surface readable while making recovery obvious.
User-Action Failure
Use a toast when an error happens in response to a user action and the surrounding surface is still usable.
This is usually the right fit for:
- failed saves
- failed deletes
- failed status changes
- action menu operations
- background mutation failures after the user already understands the current screen
In those cases, do not replace the whole page, panel, or table with ErrorState.
Prefer a toast because it:
- preserves the current context
- acknowledges the failed action immediately
- can suggest the next step without blocking the workflow
Typical pattern:
try {
await updateThing();
} catch {
showToast({
type: 'error',
title: 'Failed to Update Status',
content: 'Please try again.',
});
}If the failure blocks the current surface from functioning at all, use an in-context error surface instead of a toast.
Copy And Action Rules
Error copy should help the user recover:
- title should describe what failed
- description should say what the user can expect or try next
- primary action should be the most likely recovery path
- secondary action should usually be navigation or dismissal
Preferred recovery actions:
- retry the failed request
- go back
- close the current panel or dialog
- navigate somewhere safe
Avoid vague actions like Okay on a blocked surface when the user still needs a meaningful next step.
For toast-based action failures:
- title should name the failed action
- content should stay short and directly useful
- avoid dumping long backend error text into transient notifications
- prefer actionable follow-up when possible, such as retrying or checking a prerequisite
Empty State Is Not Error State
Do not use ErrorState as the default empty-state pattern for ordinary no-content scenarios.
Prefer:
DataTableemptyStatefor collection emptiness- a feature-owned empty-state layout when the surface is informative, aspirational, or onboarding-oriented
AlertBoxfor inline issues that do not block the surface
Some existing product code uses ErrorState for informational empty messaging, but that should not be treated as the design-system rule.
Brand Dashboard Usage
| Pattern | Path | Notes |
|---|---|---|
| Full-page fatal fallback | fsai/apps/brand-dashboard/src/pages/ErrorPage.tsx | Raw ErrorState with reload action. |
| 404 route | fsai/apps/brand-dashboard/src/pages/NotFound/NotFound.tsx | Raw ErrorState with a navigation action. |
| Grid retry state | fsai/apps/brand-dashboard/src/pages/Helpdesk/components/ArticlesGrid.tsx | ErrorGuard with serverError override and retry button. |
| Modal-body retry state | fsai/apps/brand-dashboard/src/modules/csv-ingestion/components/ListImportsDialog/ListImportsDialog.tsx | Same retry pattern inside a modal flow. |
| Panel stack fallback | fsai/apps/brand-dashboard/src/components/PanelStack/PanelStackErrorGuard.tsx | Uses Panel.ErrorGuard with back/close actions. |
| Table content failure | fsai/packages/shared-ui/src/components/DataTable/content/DataTableContentWrapper.tsx | Standard DataTable error rendering path. |
| Action-triggered failure feedback | fsai-design-system/content/docs/components/toast.mdx | Toasts are the standard transient response when a user action fails but the current surface still works. |
Relationship To Other Docs
- See the
Error Statecomponent page for the compound primitive API. - See the
Panelcomponent page for panel-specific recovery surfaces. - See the
DataTablecomponent page for collection-levelerrorStatebehavior. - See the
Alert Boxcomponent page for inline persistent errors that do not block the whole surface. - See the
Toastcomponent page andtoastspattern page for action-triggered failure feedback.
Guidelines
- Do choose the error surface based on the scope of the failure
- Do prefer
ErrorGuardwhen runtime errors should map to shared presets - Do keep panel and table failures in context instead of bouncing users to generic full-page errors
- Do use toasts for many user-action failures when the current surface remains usable
- Do make retry, back, close, and safe navigation the default recovery actions
- Don't use a full centered error state when an inline
AlertBoxis enough - Don't replace a healthy screen with a blocked error surface just because a single mutation failed
- Don't confuse empty-state messaging with blocked error-state messaging