Empty State
A placeholder component for empty lists, tables, and views. Features animated icon entrance, title, description, and a call-to-action slot.
Basic
A composable placeholder for empty lists, search results, tables, and views. Features animated icon, title, description, and a flexible action slot.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add empty-statepnpm dlx @work-rjkashyap/unified-ui add empty-statenpx @work-rjkashyap/unified-ui add empty-statebunx @work-rjkashyap/unified-ui add empty-stateIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import {
EmptyState,
Button,
} from "@work-rjkashyap/unified-ui";Basic Usage
The simplest empty state with just a title and description.
<EmptyState
title="Nothing here yet"
description="Items you create will appear here."
/>With Action
Add a action prop to render a call-to-action below the description.
Custom Icon
Replace the default inbox icon with any ReactNode via the icon prop.
Contextual Variants
Use different icons and copy to match the specific empty context.
No Icon
Pass icon={null} to render the empty state without any icon — useful for compact in-table or inline contexts.
Custom Children
Use children to render any extra content between the description and the action slot.
<EmptyState
icon={<FileText className="size-8" />}
title="No documents"
description="Create your first document to get started."
>
<div className="flex flex-col gap-1 text-xs text-muted-foreground">
<p>Supported formats: PDF, DOCX, MD</p>
<p>Max size: 25 MB per file</p>
</div>
</EmptyState>In a Table
A typical pattern is rendering EmptyState inside the table body when there are no rows.
<table>
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{data.length === 0 ? (
<tr>
<td colSpan={3}>
<EmptyState
icon={<FileText className="size-7" />}
title="No records found"
description="Create your first record to see it here."
action={
<Button variant="primary" size="sm">
Create Record
</Button>
}
className="py-10"
/>
</td>
</tr>
) : (
data.map((row) => <TableRow key={row.id} {...row} />)
)}
</tbody>
</table>In a Card
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardBody>
<EmptyState
icon={<Bell className="size-7" />}
title="No activity yet"
description="Actions you take will appear here."
className="py-8"
/>
</CardBody>
</Card>After Search
function SearchResults({ query, results }) {
if (results.length === 0) {
return (
<EmptyState
icon={<Search className="size-8" />}
title={`No results for "${query}"`}
description="Try different keywords or check for typos."
action={
<Button variant="ghost" size="sm" onClick={clearSearch}>
Clear Search
</Button>
}
/>
);
}
return <ResultList results={results} />;
}Without Animation
Set animated={false} to disable entrance animations — useful when rendering inside already-animated containers.
<EmptyState
title="No data available"
description="Check back later."
animated={false}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Custom icon. Defaults to an inbox icon. Pass null to hide the icon. |
title | ReactNode | — | Heading text displayed below the icon. |
description | ReactNode | — | Supporting text displayed below the title. |
action | ReactNode | — | Call-to-action element rendered below the description. |
animated | boolean | true | Whether to enable entrance animations. |
className | string | — | Additional CSS classes on the root container. |
children | ReactNode | — | Extra content rendered between the description and the action slot. |
Motion
- Icon — Uses the
scaleInpreset (scale: 0.85 → 1,opacity: 0 → 1, spring withstiffness: 300, damping: 24). - Text block — Uses the
fadeInpreset with a150msdelay, so the text appears slightly after the icon settles. - Action — Uses the
fadeInpreset with a250msdelay, creating a staggered entrance sequence. - All animations respect
prefers-reduced-motionviauseReducedMotion().
Accessibility
- The root element is a plain
<div>withtext-centerlayout — addrole="status"if the empty state appears dynamically after a data load to announce it to screen readers. - The default icon is
aria-hidden="true". - Title and description are
<p>elements — wrap in a heading tag if the empty state occupies its own page section. - The action slot accepts any element — ensure buttons and links within it have descriptive labels.
Design Tokens
| Token | Usage |
|---|---|
--muted | Icon container background |
--muted-fg | Icon container foreground color |
--radius-full | Icon container border radius |
--duration-fast | Transition speed for child animations |