Shadcn Empty State Table
Two different empty tables, handled separately: one with no data yet (with a call to action) and one where a search matches nothing (with a way to clear it).
$ npx shadcn add https://www.shad-table.dev/r/empty-state-table.json| Customer | Status | Amount | Due |
|---|---|---|---|
No invoices yet Invoices you create will show up here so you can track what's paid and what's overdue. | |||
How it works
1."No data" and "no results" are different problems
An empty list needs the user to create something; an empty search needs them to change the search. The table tells them apart by checking the source data length, not the filtered row count, and shows a different icon, message, and action for each.
const hasNoData = data.length === 0 // source is emptyconst rows = table.getRowModel().rows // filtered result{hasNoData ? <EmptyState title="No invoices yet" ... /> : <EmptyState title="No results found" ... />}
2.The empty state lives inside the table body
It renders in a single row with colSpan equal to the column count, so the header stays in place and the table keeps its borders and width. Collapsing to a standalone card makes the layout jump when data arrives.
<TableRow className="hover:bg-transparent"><TableCell colSpan={columns.length} className="p-0">{emptyState}</TableCell></TableRow>
3.Every empty state has a next step
EmptyState takes an optional action slot. First-use passes a Create button; no-results passes a Clear search button that resets the global filter. An empty state without an action is a dead end.
interface EmptyStateProps {icon: React.ReactNodetitle: stringdescription: stringaction?: React.ReactNode}
4.The search input is disabled when there is nothing to search
With no data, filtering can't produce anything, so the input is disabled rather than inviting a search that can only lead to a misleading "no results" message.
<Inputplaceholder="Search invoices..."disabled={hasNoData}/>