ErrorState
- Status
- Implemented
- Family
- 8 of 13
- Registry
- @lopifi/error-state
npx shadcn@latest add @lopifi/theme @lopifi/error-stateThe component, rendering
Real data, the shipped component. Switch the theme to check the claims the spec makes below.
The four-layer spec
Couldn't load transactions. ⓘ ← the technical account, one press away
No transactions were affected. ← the financial reassurance
[Try again] [Contact support]
The reassurance sentence is the most important line, and it is the only prose that stays on screen. In a financial product, an error makes users worry that something happened to their money. Saying explicitly that nothing did is worth more than the retry button — and worth more than the paragraph explaining the outage.
The detail — which endpoint, which timeout, which missing rate — is real and belongs to the person who asks for it, so it rides an InfoTip on the title rather than a third paragraph. An InfoTip and not a Tooltip: it opens on press, so the account of the failure survives touch.
Variants: network · permission · not-found · server · stale-data. Each states what happened, what it means for their money, and what to do.
Lifted verbatim from System & states. The doc and this page are the same paragraphs, so they cannot disagree.
What lands in your repository
One component is never one file. Everything below is copied into your project, where you own it.
- Files
components/ledger/error-state.tsx
- npm packages
- @base-ui-components/reactclass-variance-authorityclsxtailwind-merge
- Registry entry
- /r/error-state.json
The whole component
No compiled bundle, no black box. This is the file the registry copies, and the file you will edit.
import * as React from "react";
import { cn } from "@/lib/ledger/cn";
import { Button } from "@/components/ledger/button";
import {
ExclamationTriangleIcon,
LockClosedIcon,
MagnifyingGlassMinusIcon,
ServerStackIcon,
SignalSlashIcon,
} from "@/components/ledger/icons";
import { InfoTip } from "@/components/ledger/info-tip";
type ErrorVariant = "network" | "permission" | "not-found" | "server" | "stale-data";
const ICONS = {
network: SignalSlashIcon,
"not-found": MagnifyingGlassMinusIcon,
permission: LockClosedIcon,
server: ServerStackIcon,
"stale-data": ExclamationTriangleIcon,
} as const;
export interface ErrorStateProps extends React.ComponentPropsWithoutRef<"div"> {
/**
* The technical account of what failed. It is the least urgent thing on the
* screen and the longest, so it lives behind the ⓘ beside the title — an
* InfoTip rather than a Tooltip, because it opens on press and so survives
* touch.
*/
detail: string;
onRetry?: () => void;
onSupport?: () => void;
/**
* The one line of prose that stays visible. An error in a financial product
* makes users worry something happened to their money; saying explicitly
* that nothing did is worth more than the retry button — and worth more
* than the paragraph explaining the outage.
*/
reassurance: string;
retryLabel?: string;
title: string;
variant?: ErrorVariant;
}
export const ErrorState = React.forwardRef<HTMLDivElement, ErrorStateProps>(
(
{
className,
variant = "network",
title,
detail,
reassurance,
onRetry,
retryLabel = "Try again",
onSupport,
...props
},
ref,
) => {
const Icon = ICONS[variant];
return (
<div
className={cn(
"flex flex-col items-center gap-4 rounded-xl border border-border bg-surface px-6 py-12 text-center",
className,
)}
ref={ref}
role="alert"
{...props}
>
<div className="grid size-12 place-items-center rounded-md bg-negative-subtle">
<Icon aria-hidden="true" className="size-5 text-negative" />
</div>
<div className="max-w-[360px] space-y-1.5">
<p className="text-title-sm font-medium">
{title}
<InfoTip
about={`What happened: ${title}`}
className="text-left"
triggerClassName="ml-1.5 align-[-1px]"
>
{detail}
</InfoTip>
</p>
<p className="text-body text-text-secondary">{reassurance}</p>
</div>
<div className="flex gap-2">
{onRetry ? (
<Button onClick={onRetry} variant="primary">
{retryLabel}
</Button>
) : null}
{onSupport ? (
<Button onClick={onSupport} variant="secondary">
Contact support
</Button>
) : null}
</div>
</div>
);
},
);
ErrorState.displayName = "ErrorState";
The accounting it encodes
Concepts this component renders. Each one is defined once, in the lexicon, and used identically in the code and the copy.
Also in System & states
- EmptyStateThree variants. Always one next action.
- SkeletonStructural, per-component.
- StaleIndicatorLast-synced + retry.
- ToastSonner, configured to our tokens.
- CommandPaletteMotionless. Money-aware search.
- SheetVaul, configured. The mobile primary overlay.
- HoldToConfirmIrreversible actions, with accessible fallbacks.
- DensityTogglePersisted, synced, phone-clamped.
See also
A component is specified in one place, defined in another, and running in a third. All three, from here.
- System & states — the family specThe four-layer specification this component was built against, before it was written.Doc
- States, density and system componentsThe family running, with real figures in it, in both themes and at every density.Live
- CatalogAll 179 components, specified before they are written.Doc
- TransactionA business event — a payment, a sale, a purchaseTerm
Elsewhere in the system
Where ErrorState sits in the rest of the system — the live section it renders in, the calculators that compute what it displays, and the ledger payloads it is fed from.
- ErrorState, runningThe system showcase — live, with theme, density, greyscale and reduced-motion switches.Live
- Writes, concurrency and rate limitsWhat happens when you write, and how fast can you go?API
- System & statesThe states that decide whether a product feels finished. Specs for 9 React components: EmptyState, ErrorState, Skeleton, StaleIndicator and 5 more.Doc
Components are copied into your repository and meant to be edited — rounding policy, fiscal calendar and compliance copy differ per company.
Told when System & states changes
Components are copied into your repo, so a fix here does not reach you on its own. One email when a component in this family changes in a way that needs a migration — and nothing when it does not.