Error states

Your go-to fallback family. Three content scopes — page, section, item — pair with <ErrorBoundary> to replace failed sub-trees. The fourth scope, chrome, isn't rendered here — each chrome pattern owns its own degraded shape.

This page is the one-stop shop: when each scope kicks in, what your user sees, and what each one lets them do. The placement principle lives in /error-handling; the React mechanism lives in /error-boundary; both live side by side here.

scope="page"

Triggered when a route's render throws. SiteLayout's baked-in boundary catches it. Takes the entire ContentWell so the user knows the page they wanted isn't reachable; AppBar + Preferences + Comms stay present (outside the boundary) so they can navigate elsewhere or report it.

tsx
<ErrorBoundary fallback={<ErrorState scope="page" />}>
  {routeChildren}
</ErrorBoundary>

scope="section"

Triggered when a region inside content throws. Caught by a consumer-placed boundary around the region. A card-sized inline error in the critical Card variant — its solid critical fill makes the failure pop at a glance without taking over the page.

Section error

Couldn't load vendor risk profile

The data feed returned an error. Try again, or talk to a human via the Comms corner.

tsx
<ErrorBoundary
  fallback={
    <ErrorState
      scope="section"
      title="Couldn't load vendor risk profile"
      onReset={refetch}
    />
  }
>
  <RiskProfileWidget vendorId={vendor.id} />
</ErrorBoundary>

scope="item"

Triggered when a single row / cell / list item throws. Caught by a per-item boundary so the rest of the list keeps rendering. Tiny inline placeholder with click-to-retry semantics.

A list with one failed row:

Vendor — Stripe

Vendor — Twilio

Vendor row unavailable

Vendor — Datadog

tsx
{vendors.map((v) => (
  <ErrorBoundary
    key={v.id}
    fallback={<ErrorState scope="item" title="Vendor row unavailable" />}
  >
    <VendorRow vendor={v} />
  </ErrorBoundary>
))}

scope="chrome" — pattern-local, not rendered here

Each chrome pattern owns its own broken-state look because the shapes just don't match — an avatar circle, a brand anchor, and a corner button have nothing visual in common. The ground rules hold everywhere: keep the layout in place, keep the fallback quiet, add a tooltip hint, and (for Comms specifically) keep the transport working.

BrandSlot degraded
Plain text "Halo" in place of the artwork; anchor to home still works.
AccountSlot degraded
Empty avatar circle, no menu, tooltip explains.
PreferencesCorner degraded
Silver button in the corner, inert, tooltip explains.
CommsCorner degraded
Stays functional via direct mailto — the popover is bypassed, but the human-comms door still opens. The transport is the load-bearing concern of the corner.

Props

scope
page | section | item

REQUIRED Which content scope. The chrome scope isn't a value here — chrome fallbacks live in each chrome pattern.

title
string

Override the default title. Defaults vary by scope (e.g. page → "This page hit an error").

message
ReactNode

Override the default body. Defaults explain "the rest still works."

error
Error

The captured Error. Rendered inside a <details> for technical detail. Wire from the ErrorBoundary render-prop.

onReset
() => void

Recovery affordance. Page scope defaults to window.location.reload() when absent.

resetLabel
string

Override the recovery action label. Defaults: page → "Reload page", section → "Try again", item → "Retry".

When to reach for which

  • page — almost never explicitly. SiteLayout supplies one already. Hand-build a page-scope ErrorBoundary only if you're rolling your own Layout.
  • section — around any region whose failure is independent of the surrounding page: a remote-data widget, a chart, a paneled sub-view.
  • item — around list items where each can fail individually. The rest of the list keeps rendering.

Anti-patterns

  • Don't show item-scope copy at page scope. "Error" alone is not enough for a page-takeover; the user needs to know what to do.
  • Don't put fetches in the fallback. A fallback that throws crashes its own boundary's parent. Keep fallbacks declarative.
  • Don't hide the error from telemetry. Pair every ErrorBoundary with an onError callback that ships to Sentry. The user-facing fallback is one thing; the developer report is another.