Skip to content

Error State

What to show when a page or a panel could not be shown: a failed load, or a 404, 403 or 500 screen, with what happened and how to go on.

Feedbackv0.1.0experimentalWCAG 2.2 AAView spec

We could not load your invoices

Check your connection and try again. If it keeps happening, tell your administrator.

import { Button, ErrorState } from "@rdloom/react";

export default function ErrorStateBasicExample() {
  return (
    <div className="w-[40rem] max-w-full">
      <ErrorState
        title="We could not load your invoices"
        description="Check your connection and try again. If it keeps happening, tell your administrator."
        actions={
          <>
            <Button variant="secondary">Back to dashboard</Button>
            <Button>Try again</Button>
          </>
        }
      />
    </div>
  );
}

Installation

npx rdloom add error-state

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/error-state.json

Works, but without upgrade tracking.

Usage

import { Button, ErrorState } from "@rdloom/react";

<div className="w-[40rem] max-w-full">
  <ErrorState
    title="We could not load your invoices"
    description="Check your connection and try again. If it keeps happening, tell your administrator."
    actions={
      <>
        <Button variant="secondary">Back to dashboard</Button>
        <Button>Try again</Button>
      </>
    }
  />
</div>

Not found

404

Page not found

The page you are looking for has moved or does not exist.

import { Button, ErrorState } from "@rdloom/react";

// A whole screen: the variant is "page" and the title is the page h1.
export default function ErrorStateNotFoundExample() {
  return (
    <div className="flex w-[48rem] max-w-full">
      <ErrorState
        variant="page"
        headingLevel={1}
        code="404"
        title="Page not found"
        description="The page you are looking for has moved or does not exist."
        actions={<Button>Go to the dashboard</Button>}
      />
    </div>
  );
}

With state boundary

  • #2041 Brightwater Supplies$1,240.00
  • #2040 Harbor Mills$860.50
  • #2039 Northgate Foods$2,310.00
import { useState } from "react";
import { Button, EmptyState, ErrorState, Skeleton, StateBoundary, type DataState } from "@rdloom/react";

const invoices = [
  { id: "2041", customer: "Brightwater Supplies", total: "$1,240.00" },
  { id: "2040", customer: "Harbor Mills", total: "$860.50" },
  { id: "2039", customer: "Northgate Foods", total: "$2,310.00" },
];

// StateBoundary picks what to show for a DataState: a skeleton while loading, an empty state, an
// error state with a retry, or your data. You supply the state from your own data layer.
export default function ErrorStateWithStateBoundaryExample() {
  const [state, setState] = useState<DataState>("ready");
  return (
    <div className="flex w-[40rem] max-w-full flex-col gap-4">
      <div role="group" aria-label="Pretend the data is" className="flex flex-wrap gap-2">
        {(["loading", "empty", "error", "ready"] as const).map((s) => (
          <Button key={s} size="sm" variant={state === s ? "primary" : "secondary"} aria-pressed={state === s} onPress={() => setState(s)}>
            {s}
          </Button>
        ))}
      </div>
      <StateBoundary
        state={state}
        loading={
          <div role="status" className="flex flex-col gap-3">
            <span className="sr-only">Loading invoices</span>
            <Skeleton variant="text" lines={3} />
          </div>
        }
        empty={<EmptyState size="sm" title="No invoices yet" description="Invoices you create show up here." />}
        error={
          <ErrorState
            title="We could not load your invoices"
            description="Try again in a moment."
            actions={<Button onPress={() => setState("loading")}>Try again</Button>}
          />
        }
      >
        <ul className="divide-y divide-[var(--rd-color-border-default)] rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] text-sm">
          {invoices.map((i) => (
            <li key={i.id} className="flex items-center justify-between gap-4 px-4 py-3">
              <span className="text-[var(--rd-color-text-default)]">
                #{i.id} {i.customer}
              </span>
              <span className="text-[var(--rd-color-text-muted)]">{i.total}</span>
            </li>
          ))}
        </ul>
      </StateBoundary>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
titlerequired

Say what happened, e.g. "We could not load your invoices".

stringnone
description

Why it happened and what the person can do.

stringnone
icon

An icon above the title. Decorative. Without an icon or a code a warning icon is shown.

nodenone
code

A status code such as 404, shown large above the title.

stringnone
actions

What to do next: a Retry button, a link back.

nodenone
variant

inline for inside a card or table; page for a whole screen.

"inline" | "page""inline"
headingLevel

Heading level of the title, 1 to 6. Use 1 when the error is the whole page.

number2
announce

Announce it to screen readers (role alert). Turn it on only when the error appears after the page has loaded.

booleanfalse
classNames

Extra class names for single parts, so you can restyle one part without editing the file. Keys: root, icon, code, title, description, actions.

Partial<Record<"root" | "icon" | "code" | "title" | "description" | "actions", string>>none

Accessibility

Role none, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves to the actions

Screen readers announce

  • With announce: "Alert. We could not load your invoices."
  • Without: read in order as a heading, text and buttons

What your code must do

  • The title is a real heading
  • With announce the region has role alert and is read when it appears; without it nothing is announced, so a page that loaded with an error does not shout
  • The icon is decorative; the words carry the message, never the color

Block contract

Data
A title and description in plain words, and the actions for what to do next.
Data states
error
Permissions
None yet
Events
None
You can replace
classNames for each part; icon, code and actions slots; variant

Guidelines

Use it when

  • A request failed and you want to offer a retry
  • A 404, 403 or 500 screen

Avoid it when

  • A single field is wrong: use the field's error message
  • A short message over working content: use Alert

Don't

  • Showing the raw error text from the server
  • Setting announce on a page that loaded with the error

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-border-default
  • --rd-color-surface-subtle
  • --rd-color-feedback-danger