FSAI Design System
Patterns

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 thisWhen it fits
ErrorStateStatic or feature-owned error surfaces such as full-page fallbacks or known 404 screens
ErrorGuardA region has a runtime error object and should map it to shared presets and recovery UI
Panel.ErrorGuardPanel content failed and the user should recover inside the panel flow
DataTable errorStateA collection view failed and should preserve toolbar and table context
AlertBoxThe screen is still usable and only needs inline persistent error or warning messaging
showToastA 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, and validation
  • 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:

  • Back to the previous panel
  • Close 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:

  • error for the actual failure object
  • errorState for overrides like retry copy and actions
  • emptyState only 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:

  • DataTable emptyState for collection emptiness
  • a feature-owned empty-state layout when the surface is informative, aspirational, or onboarding-oriented
  • AlertBox for 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

PatternPathNotes
Full-page fatal fallbackfsai/apps/brand-dashboard/src/pages/ErrorPage.tsxRaw ErrorState with reload action.
404 routefsai/apps/brand-dashboard/src/pages/NotFound/NotFound.tsxRaw ErrorState with a navigation action.
Grid retry statefsai/apps/brand-dashboard/src/pages/Helpdesk/components/ArticlesGrid.tsxErrorGuard with serverError override and retry button.
Modal-body retry statefsai/apps/brand-dashboard/src/modules/csv-ingestion/components/ListImportsDialog/ListImportsDialog.tsxSame retry pattern inside a modal flow.
Panel stack fallbackfsai/apps/brand-dashboard/src/components/PanelStack/PanelStackErrorGuard.tsxUses Panel.ErrorGuard with back/close actions.
Table content failurefsai/packages/shared-ui/src/components/DataTable/content/DataTableContentWrapper.tsxStandard DataTable error rendering path.
Action-triggered failure feedbackfsai-design-system/content/docs/components/toast.mdxToasts are the standard transient response when a user action fails but the current surface still works.

Relationship To Other Docs

  • See the Error State component page for the compound primitive API.
  • See the Panel component page for panel-specific recovery surfaces.
  • See the DataTable component page for collection-level errorState behavior.
  • See the Alert Box component page for inline persistent errors that do not block the whole surface.
  • See the Toast component page and toasts pattern page for action-triggered failure feedback.

Guidelines

  • Do choose the error surface based on the scope of the failure
  • Do prefer ErrorGuard when 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 AlertBox is 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

On this page