Unified UI

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-state
pnpm dlx @work-rjkashyap/unified-ui add empty-state
npx @work-rjkashyap/unified-ui add empty-state
bunx @work-rjkashyap/unified-ui add empty-state

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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>
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

PropTypeDefaultDescription
iconReactNodeCustom icon. Defaults to an inbox icon. Pass null to hide the icon.
titleReactNodeHeading text displayed below the icon.
descriptionReactNodeSupporting text displayed below the title.
actionReactNodeCall-to-action element rendered below the description.
animatedbooleantrueWhether to enable entrance animations.
classNamestringAdditional CSS classes on the root container.
childrenReactNodeExtra content rendered between the description and the action slot.

Motion

  • Icon — Uses the scaleIn preset (scale: 0.85 → 1, opacity: 0 → 1, spring with stiffness: 300, damping: 24).
  • Text block — Uses the fadeIn preset with a 150ms delay, so the text appears slightly after the icon settles.
  • Action — Uses the fadeIn preset with a 250ms delay, creating a staggered entrance sequence.
  • All animations respect prefers-reduced-motion via useReducedMotion().

Accessibility

  • The root element is a plain <div> with text-center layout — add role="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

TokenUsage
--mutedIcon container background
--muted-fgIcon container foreground color
--radius-fullIcon container border radius
--duration-fastTransition speed for child animations

On this page