Error Boundary
A React error boundary that catches render-phase errors thrown by its subtree and replaces the broken region with a fallback. Wrap risky leaves — anything that hits the network mid-render, parses user input, or runs untrusted code — so a single bad render can't tear down the whole tree.
All systems nominal.
Customize
Fallback
Installation
npx shadcn@latest add https://craftbits.dev/r/error-boundary.jsonUsage
import { ErrorBoundary } from "@craft-bits/core";
<ErrorBoundary>
<RiskyTree />
</ErrorBoundary>Pass a custom fallback as a node, a render-prop, or a component:
<ErrorBoundary fallback={<p>Something broke.</p>}>
<RiskyTree />
</ErrorBoundary>
<ErrorBoundary
fallback={({ error, reset }) => (
<div>
<p>{error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)}
>
<RiskyTree />
</ErrorBoundary>Wire telemetry through onError, and auto-recover when route or input changes via resetKeys:
<ErrorBoundary
onError={(err, info) => telemetry.report(err, info)}
onReset={() => analytics.track("error_recovered")}
resetKeys={[route, userId]}
>
<RiskyTree />
</ErrorBoundary>Understanding the component
- Class under the hood, forwardRef on top. React's error-handling lifecycles only exist on class components.
ErrorBoundaryis exported as aforwardRef-ed functional wrapper around a private class — the forwarded ref lands on the root<div>of the rendered fallback so callers can scroll-to or measure the error region. - Fallback polymorphism. The
fallbackprop accepts aReactNodefor static content, anErrorBoundaryFallbackRenderrender-prop that receives{ error, reset }, or aComponentType<ErrorBoundaryFallbackProps>when the caller already has a dedicated fallback component. - Auto-reset via
resetKeys. When any value inresetKeyschanges between renders the boundary clears its error state and re-renders children. Use this to recover automatically when the route, the requested resource, or the form input changes. onErroris a side channel. It fires once per caught error and exists for telemetry. It cannot recover from the error — the boundary always renders the fallback.- Default screen. With no
fallbackprop, the boundary rendersDefaultErrorScreen: a polished card with an alert glyph, a headline, body copy, an optional diagnostic block, and a primary retry button. All copy is overridable. - Data hooks. The root carries
data-cb-error-boundaryanddata-state="error". The diagnostic block carriesdata-cb-error-boundary-details.
Variants
Default fallback
<ErrorBoundary>
<RiskyTree />
</ErrorBoundary>Custom render-prop fallback
<ErrorBoundary
fallback={({ error, reset }) => (
<div role="alert">
<h2>Couldn't load this section.</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
>
<RiskyTree />
</ErrorBoundary>Auto-reset on route change
<ErrorBoundary resetKeys={[pathname]}>
<RoutedPanel />
</ErrorBoundary>Telemetry hook
<ErrorBoundary onError={(err, info) => sentry.captureException(err, { extra: info })}>
<RiskyTree />
</ErrorBoundary>Props
ErrorBoundary
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The subtree protected by the boundary. |
fallback | ReactNode | ErrorBoundaryFallbackRender | ComponentType<ErrorBoundaryFallbackProps> | — | Custom fallback. Omit to use DefaultErrorScreen. |
onError | (error: Error, info: ErrorInfo) => void | — | Side-channel callback for telemetry. |
onReset | () => void | — | Fires when the boundary transitions back from error to ok. |
resetKeys | readonly unknown[] | — | Auto-reset when any value here changes. |
showErrorDetails | boolean | NODE_ENV !== "production" | Show error message in a diagnostic block on the default fallback. |
className | string | — | Merged onto the rendered fallback root via cn(). |
...rest | HTMLAttributes<HTMLDivElement> | — | Any other <div> attribute. |
ErrorBoundaryFallbackProps
| Field | Type | Description |
|---|---|---|
error | Error | The error that was caught. |
reset | () => void | Clears the boundary and re-renders children. |
DefaultErrorScreen
| Prop | Type | Default | Description |
|---|---|---|---|
error | Error | — | The caught error. |
reset | () => void | — | Retry callback. |
showErrorDetails | boolean | true | Render the error message in a diagnostic block. |
title | ReactNode | "Something went wrong" | Headline copy. |
description | ReactNode | retry hint | Body copy. |
retryLabel | string | "Try again" | Retry button label. |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- The rendered fallback root is a
role="alert"region witharia-live="assertive"so assistive tech announces the error immediately when the boundary trips. - The retry button on the default screen has a 44 px minimum hit area and a visible
:focus-visiblering keyed to--cb-accent. - The alert glyph is
aria-hiddenso screen readers don't double-announce it alongside the headline. - Colour contrast: every text token (
--cb-fg,--cb-fg-muted) clears WCAG AA on--cb-bg-elevatedin both light and dark themes. - No motion. Reduced-motion respect is the responsibility of the surrounding content.
Credits
- Extracted from:
algoflashcards(src/platform/ui/ErrorBoundary.tsx). The original used hardcoded hex fallbacks for error tone, threaded the dev-only diagnostic toggle throughimport.meta.env.DEV, and shipped an internalErrorScreenwith a hard-coded "Go home" link. craft-bits replaces the colours withcb-error/cb-accenttokens, swaps the dev gate for aNODE_ENV-drivenshowErrorDetailsprop the caller can override, drops the project-specific "Go home" link, and addsonError,onReset, andresetKeysso the boundary is composable with telemetry and route-driven recovery.