Error boundary
What shows when a page can't: a crash, a missing page, no access, offline, signed out or the database out of reach. The failure stays inside the smallest part that broke, keeps a way out, and is reported on its own.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
This page didn’t load
Try again. Nothing you saved was lost.
import { ErrorFallback } from '@/ui/error-boundary';<ErrorFallback />A page failed
The sidebar and the page header stay; only the content area is replaced, so you can go somewhere else. The same parts as the empty state: the 32px tile, a 16/20/600 title, one line and one way out. Try again is secondary: it is a retry, not the page's main action.
This page didn’t load
Try again. Nothing you saved was lost.
import { ErrorFallback } from '@/ui/error-boundary';
// The Companies page failed: the header stays, only the content area is replaced.
export default function ErrorBoundarySection() {
return (
<div className="flex w-full flex-col overflow-hidden rounded-xlarge shadow-raised">
<div className="flex h-14 items-center border-b border-border px-300 text-heading-small leading-body font-semibold tracking-heading-small text-text">Companies</div>
<div className="flex min-h-80 items-center justify-center">
<ErrorFallback onRetry={() => {}} focusAction={false} />
</div>
</div>
);
}Every page state
Each state says what happened, what to do and what was kept, and has a way out: Try again for a crash, the database out of reach or offline; Go home for a page that isn't found; Switch agency and Sign out for someone who isn't a member of this agency; Sign in again when the session ended, with the draft kept. A crash shows its error ID small, so support can find the report. A page the member's role can't see shows Page not found, so its existence isn't revealed.
This page didn’t load
Try again. Nothing you saved was lost.
Page not found
Check the address, or search with ⌘K.
You’re not a member of Northbridge
Switch to an agency you belong to, or sign out and sign in as someone else.
Ascern is unavailable
Try again in a minute. Nothing you saved was lost.
You’ve been signed out
Sign in again to carry on. Your draft is kept.
You’re offline
Check your connection, then try again. Your draft is kept.
import { ErrorFallback } from '@/ui/error-boundary';
// Every page state with its way out. The crash shows its error ID for support.
export default function ErrorBoundaryProblems() {
const card = 'rounded-xlarge shadow-raised';
return (
<div className="grid w-full grid-cols-1 gap-300 md:grid-cols-2">
<ErrorFallback problem="crash" onRetry={() => {}} eventId="4f9c1e2a7b6d4c1f" focusAction={false} className={card} />
<ErrorFallback problem="not-found" homeHref="#today" focusAction={false} className={card} />
<ErrorFallback problem="no-access" agency="Northbridge" onSwitchAgency={() => {}} onSignOut={() => {}} focusAction={false} className={card} />
<ErrorFallback problem="unavailable" onRetry={() => {}} focusAction={false} className={card} />
<ErrorFallback problem="signed-out" signInHref="#sign-in" focusAction={false} className={card} />
<ErrorFallback problem="offline" onRetry={() => {}} focusAction={false} className={card} />
</div>
);
}The app failed
Only when the root layout itself throws. There is no sidebar to keep, so one sentence replaces the whole document.
Ascern didn’t load
Reload the page. Nothing you saved was lost.
import { ErrorFallback } from '@/ui/error-boundary';
// Only when the root layout itself throws: one sentence in place of the whole document.
export default function ErrorBoundaryApp() {
return (
<div className="flex min-h-72 w-full items-center justify-center rounded-xlarge shadow-raised">
<ErrorFallback scope="app" />
</div>
);
}A panel failed
PanelError takes the place of one panel of a record page, as an error card with Try again and the error ID, while the rest of the page keeps working.
import { PanelError } from '@/ui/error-boundary';
// One panel of a record page failed; the card takes its place and the rest of the page keeps working.
export default function ErrorBoundaryPanel() {
return <PanelError className="max-w-120" onRetry={() => {}} eventId="9b1d7c3e5a2f4e8d" />;
}Catch a crash
ErrorBoundary around each panel of a record page. Press Break the notes: only the Notes panel shows the error card, the details beside it keep working, and Try again draws the notes again.
Maya Chen
Senior Data Engineer at Halcyon Bank
Notes
Called Maya on Tuesday: open to a move in January.
import { useState } from 'react';
import { Button } from '@/ui/button';
import { ErrorBoundary } from '@/ui/error-boundary';
function Notes({ broken }: { broken: boolean }) {
if (broken) throw new Error('Notes failed to draw');
return <p className="text-body leading-body text-text">Called Maya on Tuesday: open to a move in January.</p>;
}
// A record page with two panels. Press Break the notes: only that panel shows the error card; the details keep working.
export default function ErrorBoundaryLive() {
const [broken, setBroken] = useState(false);
return (
<div className="flex w-full flex-col items-start gap-200">
<div className="grid w-full grid-cols-1 gap-200 md:grid-cols-2">
<section aria-label="Details" className="flex flex-col gap-100 rounded-xlarge p-200 shadow-raised">
<h3 className="text-heading-small leading-body font-semibold text-text">Maya Chen</h3>
<p className="text-body leading-body text-text-subtle">Senior Data Engineer at Halcyon Bank</p>
</section>
<section aria-label="Notes" className="flex flex-col gap-100 rounded-xlarge p-200 shadow-raised">
<h3 className="text-heading-small leading-body font-semibold text-text">Notes</h3>
<ErrorBoundary onReset={() => setBroken(false)} onError={() => '9b1d7c3e5a2f4e8d'}>
<Notes broken={broken} />
</ErrorBoundary>
</section>
</div>
<Button size="sm" onClick={() => setBroken(true)}>
Break the notes
</Button>
</div>
);
}Environment badge
Marks a sandbox Agency on every screen, so nobody mistakes practice data for real candidates. It sits in the sidebar under the Agency name.
Inline message
One short line next to the control it is about: a field, or a table cell. The smallest of three levels, under the section message and the banner.