For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
1
Support

Error State

Composable failure feedback with centered and inline layouts, recovery actions, and opt-in support details.

Playground

Live preview
Unable to load projects
Check your connection and try again.
Retry is a visual-only demo. Details use an animated Plus/Minus Accordion with a safe example reference. Choose a solid or dashed border. Reset restores settings and closes the disclosure. Button is installed separately.
Customize Error State
Customize
error state-demo.tsx
import { CircleAlertIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ErrorState, ErrorStateContent, ErrorStateHeader, ErrorStateTitle, ErrorStateDescription, ErrorStateMedia, ErrorStateActions } from "@/components/ui/error-state";

export const ErrorStateDemo = () => (
  <ErrorState variant="centered" border="none">
    <ErrorStateMedia><CircleAlertIcon aria-hidden="true" /></ErrorStateMedia>
    <ErrorStateContent>
      <ErrorStateHeader>
        <ErrorStateTitle>{"Unable to load projects"}</ErrorStateTitle>
        <ErrorStateDescription>{"Check your connection and try again."}</ErrorStateDescription>
      </ErrorStateHeader>
      <ErrorStateActions>
        {/* Connect onClick to your application's recovery operation. */}
        <Button type="button" variant="outline" size="sm">Try again</Button>
      </ErrorStateActions>
    </ErrorStateContent>
  </ErrorState>
);

Installation

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

$ pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/error-state.json

Dependencies

Installed automatically by the registry CLI. For manual setup:

Dependencies2 required packages
  • cn
  • lucide-react
$ pnpm add cn lucide-react

Usage

import {
  ErrorState,
  ErrorStateContent,
  ErrorStateHeader,
  ErrorStateTitle,
  ErrorStateDescription,
} from "@/components/ui/error-state";
 
<ErrorState>
  <ErrorStateContent>
    <ErrorStateHeader>
      <ErrorStateTitle>Unable to load projects</ErrorStateTitle>
      <ErrorStateDescription>
        Check your connection and try again.
      </ErrorStateDescription>
    </ErrorStateHeader>
  </ErrorStateContent>
</ErrorState>;

Put optional ErrorStateMedia before ErrorStateContent. Inside Content, use Header for Title and Description, Actions for buttons or links, and Details for safe support information. All parts are optional and accept native attributes and React 19 refs. The root selects layout; it does not fetch data, catch errors, or retry operations.

Examples

Recovery action

Replace only the content that failed, keeping navigation and other working sections available. This demo switches to a successful preview when you select Try again, without making a network request. In your application, connect the action to a refetch or another recovery operation. Keep retry unavailable while that operation is pending and announce the outcome.

Unable to load projects
Check your connection and try again.
error-state-demo.tsx
"use client";

import { CircleAlertIcon } from "lucide-react";
import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
  ErrorState,
  ErrorStateActions,
  ErrorStateContent,
  ErrorStateDescription,
  ErrorStateHeader,
  ErrorStateMedia,
  ErrorStateTitle,
} from "@/components/ui/error-state";

export const ErrorStateDemo = () => {
  const [failed, setFailed] = useState(true);
  if (!failed) {
    return (
      <div className="flex flex-col items-center gap-3 text-sm">
        <p role="status">Project preview is ready.</p>
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() => setFailed(true)}
        >
          Show error again
        </Button>
      </div>
    );
  }
  return (
    <ErrorState>
      <ErrorStateMedia>
        <CircleAlertIcon aria-hidden="true" />
      </ErrorStateMedia>
      <ErrorStateContent>
        <ErrorStateHeader>
          <ErrorStateTitle>Unable to load projects</ErrorStateTitle>
          <ErrorStateDescription>
            Check your connection and try again.
          </ErrorStateDescription>
        </ErrorStateHeader>
        <ErrorStateActions>
          <Button
            type="button"
            variant="outline"
            size="sm"
            onClick={() => setFailed(false)}
          >
            Try again
          </Button>
        </ErrorStateActions>
      </ErrorStateContent>
    </ErrorState>
  );
};

Install Button separately for examples or generated playground code using it. Icons use the declared lucide-react dependency; Error State does not require Button.

Inline failure and support reference

Use variant="inline" for a localized failure beside content that still works. Set border="dashed" for a subtle dashed outline, border="solid" for a solid outline, or leave the default border="none". Border style and layout are independent. Do not offer retry for missing permissions or a missing page unless retry can actually fix the problem; offer an appropriate navigation or access-request action instead.

Changes could not be synced
Reconnect to sync your changes. Keep this tab open while you check your connection.

error-state-inline-demo.tsx
import { WifiOffIcon } from "lucide-react";

import {
  ErrorState,
  ErrorStateContent,
  ErrorStateDescription,
  ErrorStateDetails,
  ErrorStateHeader,
  ErrorStateMedia,
  ErrorStateTitle,
} from "@/components/ui/error-state";

export const ErrorStateInlineDemo = () => (
  <ErrorState variant="inline" border="dashed">
    <ErrorStateMedia>
      <WifiOffIcon aria-hidden="true" />
    </ErrorStateMedia>
    <ErrorStateContent>
      <ErrorStateHeader>
        <ErrorStateTitle>Changes could not be synced</ErrorStateTitle>
        <ErrorStateDescription>
          Reconnect to sync your changes. Keep this tab open while you check
          your connection.
        </ErrorStateDescription>
      </ErrorStateHeader>
      <ErrorStateDetails summary="Support reference">
        Reference: DEMO-1042
      </ErrorStateDetails>
    </ErrorStateContent>
  </ErrorState>
);

Details now uses Accordion with a Plus/Minus crossfade and animated panel expansion. It is closed by default. Its default summary is “Technical details”; pass a localized summary when needed. Replace its icon and expandedIcon with decorative React nodes, or pass icon={null} to hide the indicator. Use defaultOpen for an initially open uncontrolled disclosure, or open with onOpenChange for controlled state. disabled prevents interaction.

This replaces the initial native details implementation: migrate native onToggle to onOpenChange, and move detail-element refs/attributes to the new div-based Accordion root. No native details or summary elements are rendered.

Only provide a safe support reference or explicitly sanitized content. Closed content may remain mounted while an animation completes and consumer code always contains any supplied information: the disclosure is not a privacy or redaction boundary. Never put secrets, raw server messages, or production stack traces inside it.

Rendering errors with an optional boundary

Install the optional package only if your application needs a component-level React boundary:

pnpm add react-error-boundary

The integration below was checked with react-error-boundary 6.1.6. Simulate render error causes the child to throw during render; Try again resets both the boundary and the state that caused the failure. It intentionally does not show the exception message. React development tooling may log the simulated error even though the fallback handles it.

Project preview is ready.

error-state-boundary-demo.tsx
"use client";

import { CircleAlertIcon } from "lucide-react";
import { useState } from "react";
import { ErrorBoundary } from "react-error-boundary";

import { Button } from "@/components/ui/button";
import {
  ErrorState,
  ErrorStateActions,
  ErrorStateContent,
  ErrorStateDescription,
  ErrorStateHeader,
  ErrorStateMedia,
  ErrorStateTitle,
} from "@/components/ui/error-state";

const ProjectPreview = ({
  fail,
  onFail,
}: {
  fail: boolean;
  onFail: () => void;
}) => {
  if (fail) {
    throw new Error("Intentional demo failure");
  }
  return (
    <div className="flex flex-col items-center gap-3 text-sm">
      <p role="status">Project preview is ready.</p>
      <Button type="button" variant="outline" size="sm" onClick={onFail}>
        Simulate render error
      </Button>
    </div>
  );
};

export const ErrorStateBoundaryDemo = () => {
  const [fail, setFail] = useState(false);
  return (
    <ErrorBoundary
      onReset={() => setFail(false)}
      fallbackRender={({ resetErrorBoundary }) => (
        <ErrorState>
          <ErrorStateMedia>
            <CircleAlertIcon aria-hidden="true" />
          </ErrorStateMedia>
          <ErrorStateContent>
            <ErrorStateHeader>
              <ErrorStateTitle>Unable to display this preview</ErrorStateTitle>
              <ErrorStateDescription>
                Try again to reset the preview. The rest of the page is still
                available.
              </ErrorStateDescription>
            </ErrorStateHeader>
            <ErrorStateActions>
              <Button
                type="button"
                variant="outline"
                size="sm"
                onClick={resetErrorBoundary}
              >
                Try again
              </Button>
            </ErrorStateActions>
          </ErrorStateContent>
        </ErrorState>
      )}
    >
      <ProjectPreview fail={fail} onFail={() => setFail(true)} />
    </ErrorBoundary>
  );
};

For production reporting, connect the boundary's onError to your error reporting service. Report privately rather than exposing raw errors to the user. Resetting a boundary retries rendering; it does not automatically retry a failed request or repair its cause.

Boundaries catch descendant rendering errors, not ordinary event-handler exceptions, SSR errors, or arbitrary rejected promises. Handle request errors explicitly and render Error State from your application's error state. Use the library's supported hooks when deliberately forwarding a caught async error to a boundary. Next.js applications can also use this UI in their framework-provided route error fallback; Error State imports no Next.js APIs.

Consumer setup and optional stories

Error State requires React 19 and Tailwind CSS 4. The CLI installs cn, lucide-react, and the Accordion registry dependency (which declares @base-ui/react, motion, cn, and lucide-react). Load your global Tailwind stylesheet with shadcn semantic tokens including foreground, muted, muted-foreground, background, border, destructive, and ring. No provider or extra animation CSS is needed. The visual root remains server-compatible; Details is a separate client module. State, event handlers, and boundary integrations belong in a client module in React Server Component applications.

The registry installs error-state.tsx and error-state-details.tsx, plus Accordion, into the UI alias configured in components.json. Installing it can update an existing Accordion, so review the CLI overwrite prompt before replacing local customizations. It does not install Empty, Button, or an error boundary. Use CLI installation: copying the displayed Source alone is insufficient because Details is a separate supporting file. Examples and playground adapters are website-only, not distributed source.

Install Error State first, then optionally install its portable stories:

$ pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/error-state-stories.json

Use your existing @storybook/react setup with matching Storybook package versions (types and composition checked with 10.6.1). Discover **/*.stories.tsx in your UI directory and load your global theme stylesheet in the preview. Stories use native action buttons, inherit the consumer theme, and use the component's declared icon dependency without extra addons or providers. The stories-only installation does not reinstall the component or modify Storybook configuration. This project has no Storybook server; Storybook UI appearance is not verified here.

Behavior & Accessibility

Error State is presentational. Keep loading, failed requests, missing permissions, missing pages, and genuinely empty results distinct. Content and actions are caller-owned; no automatic announcements, focus changes, hardcoded icon, or built-in retry callback are added.

The root and layout parts render div elements. Title is visual text, not an automatic heading. For a named region, set role="region" and aria-labelledby on the root and a matching id on Title; add role="heading" and an appropriate aria-level to Title when the page hierarchy requires it. Hide decorative icons using aria-hidden="true". Use native buttons for actions and links for navigation; do not rely on red alone to communicate the error.

Choose application-level announcements according to urgency, usually a polite live region for async feedback. Do not make every static fallback an assertive alert. When content containing focus is replaced, manage focus from the application and return it to an appropriate destination after recovery.

Details inherits Accordion's button/heading/panel semantics and keyboard interaction. It supports controlled open/onOpenChange and uncontrolled defaultOpen. Opening animates the panel and Plus/Minus indicators together; closing content becomes inert and hidden from assistive technology immediately. Reduced-motion preferences make transitions immediate. Long text wraps and the support content is keyboard-scrollable.

Playground title and description are composed children. Icon, retry, and details toggles are demo controls, not ErrorState props. The playground retry is intentionally inert; the runnable examples demonstrate recovery. Reset restores configuration and remounts the preview, closing the disclosure.

Props

Component props, types, defaults, and descriptions
PropType / DescriptionDefault
border"none" | "solid" | "dashed"

ErrorState: optional solid or dashed outline using the theme border token.

"none"
childrenReactNode

All parts: explicit content. Details does not redact supplied content; only provide safe, non-sensitive information.

Not set
classNamestring

All parts: classes merged with defaults through cn. The root has no border by default.

Not set
defaultOpenboolean

ErrorStateDetails: initial uncontrolled open state.

false
descriptionReactNode (composition)

ErrorStateDescription children. Explain a useful recovery step without blaming the user.

Not set
disabledboolean

ErrorStateDetails: disables disclosure interaction.

false
expandedIconReactNode

ErrorStateDetails: expanded-state indicator. Crossfades with the closed icon.

MinusIcon
iconReactNode

ErrorStateDetails: closed-state indicator React node. null hides the indicator.

PlusIcon
onOpenChange(open: boolean) => void

ErrorStateDetails: requests an open-state change when the Accordion trigger is activated.

Not set
openboolean

ErrorStateDetails: controlled open state; pair with onOpenChange. No details are rendered unless composed explicitly.

Uncontrolled
roleAriaRole

ErrorState: choose a region or announcement policy at the application level. No automatic alert, focus movement, or live region.

Not set
showActionboolean (demo only)

Demo composition: includes an inert retry button. Connect its onClick to your application's recovery operation.

true (demo)
showDetailsboolean (demo only)

Demo composition: includes an initially closed Accordion disclosure with a safe example reference.

false (demo)
showMediaboolean (demo only)

Demo composition: includes ErrorStateMedia with a decorative CircleAlert icon.

true (demo)
summaryReactNode

ErrorStateDetails: accessible disclosure label. Keep it nonempty and free of interactive children.

"Technical details"
titleReactNode (composition)

ErrorStateTitle children, not a prop on ErrorState. Renders a div; supply heading semantics when appropriate.

Not set
variant"centered" | "inline"

ErrorState: centered replacement content or a compact, leading-icon inline layout.

"centered"

The reference lists component-specific props and selected native props, not the complete React API. Other native attributes are forwarded to the rendered element.

Source

export {
  ErrorState,
  ErrorStateActions,
  ErrorStateContent,
  ErrorStateDescription,
  ErrorStateHeader,
  ErrorStateMedia,
  ErrorStateTitle,
} from "./error-state-base";
export type { ErrorStateProps } from "./error-state-base";
export { ErrorStateDetails } from "./error-state-details";
export type { ErrorStateDetailsProps } from "./error-state-details";
 

Credits

  • shadcn/ui Empty: Composition and visual vocabulary reference; Error State is an independent implementation, not an adapted upstream error component.
  • react-error-boundary: Optional MIT-licensed dependency used only by the rendering-error integration example.