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.
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-stateCopies 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.jsonWorks, 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
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.
| Prop | Type | Default |
|---|---|---|
titlerequiredSay what happened, e.g. "We could not load your invoices". | string | none |
descriptionWhy it happened and what the person can do. | string | none |
iconAn icon above the title. Decorative. Without an icon or a code a warning icon is shown. | node | none |
codeA status code such as 404, shown large above the title. | string | none |
actionsWhat to do next: a Retry button, a link back. | node | none |
variantinline for inside a card or table; page for a whole screen. | "inline" | "page" | "inline" |
headingLevelHeading level of the title, 1 to 6. Use 1 when the error is the whole page. | number | 2 |
announceAnnounce it to screen readers (role alert). Turn it on only when the error appears after the page has loaded. | boolean | false |
classNamesExtra 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