Source: https://monarchui.dev/components/error-state/

Components · Status and feedback

# ErrorState

A region failed to load: what failed, why in plain words, and a retry.

@rookcom/monarch-react

Component

## Error with retry and request id

## Usage examples

### Forbidden and offline

### Panel error boundary

## Purpose

ErrorState says what failed to load in a list, panel or card and offers a retry. ErrorStateBoundary catches a render error in one panel and shows an ErrorState instead of breaking the page.

### Use when

- A list or panel failed to load (`onRetry`).
- The person may not see a record (`kind="forbidden"`), or it no longer exists (`kind="not-found"`).
- A request never reached the server (`kind="offline"`).
- One pane of a multi-pane screen should fail on its own (ErrorStateBoundary).

### Avoid when

- There is simply no data: use EmptyState.
- A whole route failed or is missing: use ErrorPage.
- A small section failed inside a card that otherwise works: use InlineErrorRetry.
- Content is still loading: use LoadingState.

## Design principles

- ErrorState is an EmptyState with a kind icon and a primary "Try again" button.
- Plain language only. The product maps its errors to a kind (`errorKindFromStatus` helps) and writes the copy; never show a raw error message, stack or vendor name.
- Retry is offered only when it can help: `forbidden` and `not-found` never render it.
- The request id is there for support conversations: monospace, selectable and copyable.

## API reference

```tsx
import { ErrorState, ErrorStateBoundary, errorKindFromStatus, errorKindIcon } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `ErrorState` | component | server-safe | `.mn-error-state` |
| `ErrorStateBoundary` | component | client | `.mn-error-state` |
| `errorKindFromStatus` | utility | server-safe | – |
| `errorKindIcon` | utility | server-safe | – |

ErrorState accepts every EmptyState prop except `kind`, `icon` and `actions`, which it redefines; the ref is forwarded to the root `<div>`.

`errorKindFromStatus(status, { offline })` maps an HTTP status to a kind: 0 or offline → `offline`, 401/403 → `forbidden`, 404/410 → `not-found`, 5xx → `server`, anything else → `error`. `maintenance` is never inferred.

`errorKindIcon(kind)` returns the default decorative glyph for a kind, for products composing their own layout.

ErrorStateBoundary is a client component (React error boundary): `fallback` (node or `({ error, reset }) => node`), `onError`, `onReset` and `resetKeys`; without `fallback` it renders ErrorState with the given `title`, `description` and a retry that resets it.

LoadingState moved to its own folder in 0.4 (`loading-state`); it is still exported from `@rookcom/monarch-react` with the same props and class names.

Generated from the TypeScript declarations of `packages/react/src/components/feedback/error-state`.

## Properties

### `ErrorState`

Something failed to load. Say what failed and offer a retry.

| Prop | Type | Description |
| --- | --- | --- |
| `title` required | `ReactNode` |  |
| `actions` | `ReactNode` | Extra actions next to retry. |
| `children` | `ReactNode` | Extra content after the actions, such as a list of next steps. |
| `className` | `string` |  |
| `clearFiltersLabel` | `string` | Label of the clear-filters button. Default "Clear filters". |
| `description` | `ReactNode` |  |
| `headingLevel` | `EmptyStateHeadingLevel` | Title element. Default `h3`. Match the page outline (no skipped levels); use `p` where a heading would be wrong, such as inside a table cell. |
| `icon` | `ReactNode` | Replaces the kind's default icon. |
| `id` | `string` |  |
| `kind` | `ErrorKind` | What went wrong. Sets the default icon and whether retry is offered. Exposed as `data-kind`. |
| `layout` | `"stack" \| "line"` | `stack` (default) centres icon, text and actions in a column; `line` is a single left-aligned row (icon, text, actions) for a list or dashboard section that has nothing in it. |
| `onClearFilters` | `(() => void)` | With `kind="filtered"`, renders a "Clear filters" button before any other actions. The product resets its own filters. |
| `onRetry` | `(() => void)` | Retry handler; renders a "Try again" button (not for `forbidden` or `not-found`). |
| `requestId` | `string` | The request id from the failed response, shown in monospace with a copy button so the person can quote it to support. |
| `requestIdLabel` | `string` | Label before the request id. Default "Request ID". |
| `retrying` | `boolean` | Shows the retry button as busy while a retry is in flight. |
| `retryLabel` | `string` |  |
| `size` | `"inline" \| "page" \| "compact"` | `inline` (default) sits inside a card or table; `page` fills an empty view; `compact` is a tighter block for table bodies, side panels and small cards. |
| `tip` | `ReactNode` | A short hint under the actions, such as a keyboard shortcut (compose it with Kbd). Shown in small muted text. |

### `ErrorStateBoundary`

Error boundary for one panel or region: a render error inside it shows an ErrorState with "Try again" instead of taking down the page. The rest of the page keeps working.

| Prop | Type | Description |
| --- | --- | --- |
| `title` required | `ReactNode` |  |
| `children` | `ReactNode` |  |
| `description` | `ReactNode` |  |
| `fallback` | `ReactNode \| ((props: ErrorStateBoundaryFallbackProps) => ReactNode)` | Replaces the default fallback (an ErrorState with a retry that resets the boundary). A function receives the error and `reset`. |
| `headingLevel` | `EmptyStateHeadingLevel` | Title element. Default `h3`. Match the page outline (no skipped levels); use `p` where a heading would be wrong, such as inside a table cell. |
| `onError` | `((error: Error, info: ErrorInfo) => void)` | Report the error (logging, monitoring). Called once per caught error. |
| `onReset` | `(() => void)` | Called after the boundary resets, from the retry button or a `resetKeys` change. |
| `requestId` | `string` | The request id from the failed response, shown in monospace with a copy button so the person can quote it to support. |
| `requestIdLabel` | `string` | Label before the request id. Default "Request ID". |
| `resetKeys` | `readonly unknown[]` | When any value changes (by `Object.is`) while an error is shown, the boundary resets. Pass the inputs the failed panel depends on, such as the selected record id. |
| `retryLabel` | `string` |  |
| `size` | `"inline" \| "page" \| "compact"` | `inline` (default) sits inside a card or table; `page` fills an empty view; `compact` is a tighter block for table bodies, side panels and small cards. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `kind` | `'error'``'forbidden'``'not-found'``'offline'``'server'``'maintenance'` | Sets the default icon and tint and whether retry is offered. Non-default kinds are exposed as `data-kind`. |
| `size` | `'inline'``'page'``'compact'` | From EmptyState. `compact` fits a side panel or table body. |
| `retryLabel` | `'string'` | Retry button text. Default "Try again". |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Error with retry | – | A primary retry button is rendered when `onRetry` is set and the kind allows it, before any extra `actions`. |
| Retrying | `aria-busy="true" on the retry button` | `retrying` shows the Button loading state: a spinner, clicks blocked, focus kept on the button. |
| Forbidden / not found | `data-kind` | Neutral icon tile, no retry button. |
| Offline | `data-kind="offline"` | Warning-tinted icon tile; retry stays available. |

## Interaction behaviour

- The retry button calls `onRetry`; while `retrying` is true further clicks are ignored.
- The request-id copy button copies the id and announces "Copied to clipboard".
- ErrorStateBoundary: "Try again" clears the error and renders the children again; a change in `resetKeys` does the same.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab / Shift+Tab | Moves through retry, extra actions and the copy button. |
| Enter / Space | Activates the focused button. |

## Accessibility

Role

alert

Accessible name

–

- ErrorState has role="alert", so it is announced when it appears.
- The icon tile is `aria-hidden`; the title carries the meaning.
- While retrying, the retry button stays focusable (aria-disabled + aria-busy), so keyboard users keep their place.
- The copy button is named "Copy Request ID" (from `requestIdLabel`).
- Verified with axe in jsdom and in Chromium, Firefox and WebKit on /components/error-state.

## Responsive behaviour

- Centred with a 28rem maximum width (from EmptyState); actions wrap.
- Long request ids break anywhere instead of overflowing at 320px.

## Theme support

- The `error` and `server` icon tiles use the danger subtle and strong tokens; `offline` uses warning; `forbidden`, `not-found` and `maintenance` use the muted surface.
- Colours are Monarch semantic tokens only, so light, dark and more-contrast themes apply automatically.

## Do and don't

### Do

- Say what failed in the title.
- Pass the request id when your API returns one.
- Map statuses to kinds with `errorKindFromStatus`, then pick your own copy per kind.
- Wrap independent panes in ErrorStateBoundary so one failure does not blank the screen.

### Don't

- Do not show raw error messages, codes or vendor names as the title.
- Do not offer retry for a permission error.

## Related components

- [EmptyState](https://monarchui.dev/components/empty-state/): No data yet.
- [InlineErrorRetry](https://monarchui.dev/components/inline-error-retry/): Compact retry for one section inside a card.
- [ErrorPage](https://monarchui.dev/components/error-page/): The whole route failed or is missing.
- [LoadingState](https://monarchui.dev/components/loading-state/): Shown before the data arrives.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Button](https://monarchui.dev/components/button/), [CopyButton](https://monarchui.dev/components/copy-button/), [EmptyState](https://monarchui.dev/components/empty-state/) |

## Testing coverage

- ErrorState is an alert and calls `onRetry`; retry renders before extra actions.
- Default props keep the 0.3 markup (no `data-kind`).
- `forbidden` and `not-found` hide retry; `offline` keeps it.
- `retrying` keeps focus on the busy button and blocks repeat activation.
- The request id renders as code with a named copy button.
- `errorKindFromStatus` maps statuses and offline to kinds.
- ErrorStateBoundary catches a render error without showing its message, calls `onError`, resets on retry and on `resetKeys` change, and accepts a render-prop fallback.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/error-state |

Tests: `packages/react/src/components/feedback/error-state`

## All example code

### Error with retry and request id

```tsx
import { ErrorState } from '@rookcom/monarch-react';

<ErrorState
  title="Couldn't load invoices"
  description="Check your connection and try again."
  onRetry={() => {}}
  requestId="req_7f3a91c2"
/>
```

### Forbidden and offline

```tsx
import { Button, ErrorState } from '@rookcom/monarch-react';

<ErrorState
  kind="forbidden"
  size="compact"
  title="You can't view this project"
  description="Ask a workspace admin for access."
  actions={<Button>Request access</Button>}
/>
<ErrorState
  kind="offline"
  size="compact"
  title="You're offline"
  description="We'll show the latest data once you reconnect."
  onRetry={() => {}}
/>
```

### Panel error boundary

```tsx
import { ErrorStateBoundary } from '@rookcom/monarch-react';

<ErrorStateBoundary
  title="This panel didn't load"
  description="The rest of the page still works."
>
  <p>Panel content renders here until it throws.</p>
</ErrorStateBoundary>
```
