# ErrorBoundary

> Catches rendering errors in part of the screen and shows a recoverable fallback.

Category: Advanced · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/error-boundary

Wrap independent regions, such as a signature preview, a chart or an integration panel, so a failure in one does not blank the whole page. The default fallback is an accessible alert with a retry button; you can supply your own. ErrorBoundary catches rendering errors only. Handle event and async errors where they happen.

## Import

```tsx
import { ErrorBoundary } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Default fallback

Toggle the failure to see the fallback, then retry.

```tsx
import * as React from 'react';
import { Button, ErrorBoundary } from '@bindshq/stance-react';

function SignaturePreview({ broken }: { broken: boolean }) {
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  if (broken && mounted)
    throw new Error('Template HTML could not be parsed: unexpected </td> at line 14');
  return (
    <div
      style={{ padding: 16, border: '1px solid var(--st-color-border-default)', borderRadius: 12 }}
    >
      <strong>Priya Nair</strong>
      <div>Account Executive · Binds</div>
    </div>
  );
}

export function Basic() {
  const [broken, setBroken] = React.useState(true);
  return (
    <div style={{ display: 'grid', gap: 12, maxWidth: 460 }}>
      <Button size="sm" variant="outline" onClick={() => setBroken((b) => !b)}>
        {broken ? 'Fix the template' : 'Break the template'}
      </Button>
      <ErrorBoundary resetKeys={[broken]}>
        <SignaturePreview broken={broken} />
      </ErrorBoundary>
    </div>
  );
}
```

### Custom fallback

```tsx
import * as React from 'react';
import { Button, ErrorBoundary } from '@bindshq/stance-react';

function SignaturePreview({ broken }: { broken: boolean }) {
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  if (broken && mounted)
    throw new Error('Template HTML could not be parsed: unexpected </td> at line 14');
  return (
    <div
      style={{ padding: 16, border: '1px solid var(--st-color-border-default)', borderRadius: 12 }}
    >
      <strong>Priya Nair</strong>
      <div>Account Executive · Binds</div>
    </div>
  );
}

export function CustomFallback() {
  return (
    <div style={{ maxWidth: 460 }}>
      <ErrorBoundary
        fallback={({ reset }) => (
          <div
            style={{
              padding: 16,
              border: '1px dashed var(--st-color-border-strong)',
              borderRadius: 12,
            }}
          >
            <p style={{ marginTop: 0 }}>The preview isn’t available. Your template is saved.</p>
            <Button size="sm" onClick={reset}>
              Reload preview
            </Button>
          </div>
        )}
      >
        <SignaturePreview broken />
      </ErrorBoundary>
    </div>
  );
}
```

### Reset on navigation

Changing resetKeys clears the error, e.g. when the user opens another template.

```tsx
import * as React from 'react';
import { Button, ErrorBoundary } from '@bindshq/stance-react';

function SignaturePreview({ broken }: { broken: boolean }) {
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);
  if (broken && mounted)
    throw new Error('Template HTML could not be parsed: unexpected </td> at line 14');
  return (
    <div
      style={{ padding: 16, border: '1px solid var(--st-color-border-default)', borderRadius: 12 }}
    >
      <strong>Priya Nair</strong>
      <div>Account Executive · Binds</div>
    </div>
  );
}

export function ResetKeys() {
  const [template, setTemplate] = React.useState('broken');
  return (
    <div style={{ display: 'grid', gap: 12, maxWidth: 460 }}>
      <div style={{ display: 'flex', gap: 8 }}>
        <Button
          size="sm"
          variant={template === 'broken' ? 'primary' : 'outline'}
          onClick={() => setTemplate('broken')}
        >
          Legacy template
        </Button>
        <Button
          size="sm"
          variant={template === 'ok' ? 'primary' : 'outline'}
          onClick={() => setTemplate('ok')}
        >
          Standard template
        </Button>
      </div>
      <ErrorBoundary resetKeys={[template]} title="This template can’t be previewed">
        <SignaturePreview broken={template === 'broken'} />
      </ErrorBoundary>
    </div>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| ErrorBoundary | `children` | `ReactNode` | None | The region to protect. |
| ErrorBoundary | `fallback` | `ReactNode \| ({ error, reset }) => ReactNode` | None | Custom fallback. Defaults to an accessible alert with a retry button. |
| ErrorBoundary | `onError` | `(error: Error, info: ErrorInfo) => void` | None | Report the error and component stack to your error tracker. |
| ErrorBoundary | `onReset` | `() => void` | None | Called after the boundary resets. |
| ErrorBoundary | `resetKeys` | `readonly unknown[]` | None | When any value changes, a caught error is cleared. |
| ErrorBoundary | `title` | `string` | `'Something went wrong'` | Default fallback title. |
| ErrorBoundary | `description` | `string` | None | Default fallback description. |
| ErrorBoundary | `retryLabel` | `string` | `'Try again'` | Default retry button label. |

## Accessibility

- The default fallback uses role="alert", so the failure is announced when it appears.
- Technical details are in a collapsed disclosure, so they do not overwhelm the message.
- The retry button is a native button. After a reset, the region renders again in place.
- Write fallback copy that says what failed and what the person can do next.

## Keyboard

| Keys | Action |
| --- | --- |
| `Enter` `Space` | Activates the retry button in the default fallback. |

## Guidelines

**Do**

- Wrap independent regions separately so the rest of the screen keeps working.
- Send errors to monitoring with onError.

**Don’t**

- Wrap the entire app in a single boundary only.
- Show raw stack traces to end users.

## Related

- https://stanceui.com/components/alert
- https://stanceui.com/components/empty-state
