Catches a render error from anywhere below it and shows a fallback in place of that part of the page, instead of letting React take the whole application down to a blank screen: one widget on a dashboard, a third-party embed or ad slot, a chart handed a malformed series, a route or layout boundary in a Next.js app, a single row of a list, a markdown or rich-text renderer fed user content, a map or canvas that fails on one device. Common asks it answers: "react error boundary", "error boundary shadcn", "shadcn error boundary component", "react-error-boundary alternative", "error boundary typescript", "componentDidCatch in a function component", "error boundary hook", "catch an error in a react component", "react white screen of death", "one component crashes and takes the whole app down", "error boundary with a retry button", "reset error boundary on route change", "error boundary resetKeys", "resetKeys not working", "error boundary keeps resetting in a loop", "useErrorBoundary showBoundary", "error boundary not catching my error", "error boundary does not catch event handler errors", "error boundary async error", "catch a promise rejection with an error boundary", "next.js error.tsx alternative", "fallback ui when a component throws", "withErrorBoundary hoc", "エラーバウンダリ react", "React エラー境界", "画面が真っ白になる react", "一箇所のエラーでアプリ全体が落ちる", "エラー時のフォールバック表示", "リトライできるエラー表示". Official shadcn/ui has nothing of the kind, and that is measured rather than remembered: its catalogue today lists 65 entries of which 62 are fetchable (code-block, questionnaire and trace 404) for 223,287 bytes of component source, and in all of it componentDidCatch, getDerivedStateFromError, ErrorBoundary, errorInfo, resetErrorBoundary and fallbackRender are every one of them zero hits. So is Suspense, and so is the word class -- official is 62 function components with no error handling anywhere in them. An agent asked for a boundary therefore writes one, and this is the one component in a modern React codebase that cannot be a function: getDerivedStateFromError and componentDidCatch have no hook equivalent, so it is a class or it does not exist. The dozen lines that class takes are not the reason to install one. Four things are. First, clearing the error is not recovering. Rendering the fallback removes the children from the tree, so React does unmount them and a reset really does mount them fresh with their state gone -- but what made them throw is usually a prop or something in a store, and that has not moved, so a retry that only clears the flag throws again on the next frame. onReset is where the caller drops the bad response or bumps the query key; resetKeys is where it says what counts as the cause having changed. Second, a reset loop is easy to write by accident, in two separate ways, and both are closed here. resetKeys is compared element by element rather than by array identity, because resetKeys={[id]} builds a fresh array on every render of the parent and an identity check reads that as a change every time -- clear, mount, throw, repeat. And the keys are snapshotted as each render sees them rather than compared against prevProps in componentDidUpdate, which is what makes the ordinary case right: when a parent passes a new id and the child chokes on that new id in the same pass, comparing with prevProps reads it as "the keys changed while an error was showing", resets, and lands back on the same throw one render later. A child that throws again under keys that have not moved simply stays in the fallback. Third, most of the errors in an application never reach a boundary at all, and this is the gap that makes people think theirs is broken. React catches what is thrown while it renders and nothing else, so an onClick that throws, a setTimeout, a rejected promise, a subscription callback and the server render all go straight past it and leave the user looking at a page that quietly did nothing. useErrorBoundary() returns showBoundary, which stores the value and rethrows it from that component's next render -- a real render, so the boundary catches it with a real component stack and onError reports it like any other. load().then(setData, showBoundary) is the whole pattern. Nothing has to be cleared afterwards, because the component holding it is replaced by the fallback and unmounts. Fourth, the crash takes focus with it. The element that had it is no longer in the document, so the browser drops focus to the body and a keyboard or screen-reader user is silently returned to the top of the page with no sign that anything happened. The built-in fallback takes focus back -- and only then: not when the children threw on their first render, because nothing the user was looking at went away, and not when focus is still on something real elsewhere on the page, because that belongs to whoever is using it. What it cannot do is stated rather than hidden, since a boundary that silently misses is worse than none. It cannot catch an error thrown by its own render, which includes the fallback, so a fallback that throws goes to the boundary above and keep it to markup. It does nothing on the server: componentDidCatch is a commit-phase lifecycle and the server never commits. And it does not log, because React already prints a recovered error and its component stack to the console in development and a second copy helps nobody. Smaller things it gets right. Anything can be thrown in JavaScript, so a non-Error value arrives wrapped with the original kept on cause, and a fallback reading error.message renders something rather than "undefined"; an Error from an iframe, a worker or a vm context fails instanceof and is matched on having a string message instead of being discarded; a thrown symbol does not become a TypeError from String() replacing the application's error with one from the boundary. onError fires once per error rather than once per commit, and again if the same error comes back after a retry. Both error.message and React's componentStack routinely carry whatever was being rendered -- an email address, an order, an id -- so treat them as user data when the destination is somebody else's service, and the built-in fallback does not show the message to the reader at all unless you ask it to. The fallback is a role="alert" notice with a heading and a Try again button, announced on arrival because it arrives, focusable only programmatically so an error notice does not add a tab stop to the page, and themed through border-destructive, bg-card and the rest rather than colours of its own. Pass fallback as a node, as a function of { error, reset, recoverFocus }, or as null for a decorative widget that should just disappear; ErrorFallback is exported so a custom one can keep the same shape, and withErrorBoundary wraps a route or page component whose own file is the only place you can reach. Within pulld it is the last resort rather than the first: form-error-summary handles the errors a person can fix, network-status the connection being gone, toast and save-status the ones an operation reports, empty-state the case where there is simply nothing to show. This one is for the case nobody planned for. Depends only on your cn util -- no packages, no other components.
pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/error-boundary.json"