Search docs

Jump to any table example

Discord—

Shadcn Error State Table

A failed fetch shown two ways: a full error panel when there is nothing to display, and an inline banner above the last loaded rows when stale data is still useful. Both come with a retry action.

$ npx shadcn add https://www.shad-table.dev/r/error-state-table.json
CustomerStatus
Amount
Due

How it works

1.The error shape depends on whether data exists

If rows are already on screen, a failed refresh shouldn't wipe them: the table keeps the stale rows and shows a banner above. If there is nothing to show, the error takes the place of the table body. One hasStaleData check picks between them.

src/components/error-state/data-table.tsx
const hasStaleData = rows.length > 0
{error && hasStaleData && <ErrorState variant="banner" ... />}
// ...no rows:
{error ? <ErrorState title="Something went wrong" ... /> : null}

2.One component, two variants

ErrorState renders a centered panel by default and a compact horizontal banner with variant="banner". They share the same retry button and wording props, so the message stays consistent wherever it appears.

src/components/error-state/error-state.tsx
export function ErrorState({
title, description, onRetry, isRetrying = false,
variant = 'panel', // 'panel' | 'banner'
}: ErrorStateProps)

3.Retry shows its own progress

While a retry is in flight the button is disabled, its icon spins, and its label changes to "Retrying...". That prevents duplicate requests and tells the user something is happening.

src/components/error-state/error-state.tsx
<Button variant="outline" size="sm" onClick={onRetry} disabled={isRetrying}>
<RotateCw className={cn(isRetrying && 'animate-spin')} />
{isRetrying ? 'Retrying...' : 'Try again'}
</Button>

4.role="alert" announces the failure

Both variants use role="alert", so screen readers announce the error when it appears instead of leaving the user waiting on a table that will never fill.

src/components/error-state/error-state.tsx
<div role="alert" className="...">