Empty State
Displays a placeholder when content is empty or unavailable.
Overview
The EmptyState component provides a consistent way to communicate that a section has no content to display. By default it fills the available parent space, centers its content, and renders a solid surface shell. Use variant="ghost" when the parent already owns the surrounding surface, and use variant="overlay" when the empty state should sit above existing content.
Usage
import {
EmptyState,
EmptyStateDescription,
EmptyStateHeading,
EmptyStateMedia,
EmptyStateTitle,
} from "@tilt-legal/cubitt-components/primitives";
import { Inbox } from "@tilt-legal/cubitt-icons/ui/outline";<EmptyState>
<EmptyStateMedia>
<Inbox />
</EmptyStateMedia>
<EmptyStateHeading>
<EmptyStateTitle>No projects found</EmptyStateTitle>
<EmptyStateDescription>
Create your first project to get started with your workflow.
</EmptyStateDescription>
</EmptyStateHeading>
</EmptyState>Examples
Ghost
Use variant="ghost" when the parent already provides the border, background, or padding.
import {
EmptyState,
EmptyStateDescription,
EmptyStateHeading,
EmptyStateMedia,
EmptyStateTitle,
} from "@tilt-legal/cubitt-components/primitives";
import { Folder } from "@tilt-legal/cubitt-icons/ui/outline";
<EmptyState variant="ghost">
<EmptyStateMedia>
<Folder />
</EmptyStateMedia>
<EmptyStateHeading>
<EmptyStateTitle>No folders</EmptyStateTitle>
<EmptyStateDescription>
Create a folder to organize your files.
</EmptyStateDescription>
</EmptyStateHeading>
</EmptyState>Overlay
Use variant="overlay" when the empty state should sit on top of existing content, such as a table with skeleton rows.
import {
EmptyState,
EmptyStateDescription,
EmptyStateHeading,
EmptyStateMedia,
EmptyStateTitle,
Skeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@tilt-legal/cubitt-components/primitives";
import { Magnifier } from "@tilt-legal/cubitt-icons/ui/outline";
<div className="relative">
<EmptyState className="top-10" variant="overlay">
<EmptyStateMedia>
<Magnifier />
</EmptyStateMedia>
<EmptyStateHeading>
<EmptyStateTitle>No results</EmptyStateTitle>
<EmptyStateDescription>
Try adjusting your filters.
</EmptyStateDescription>
</EmptyStateHeading>
</EmptyState>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-48">Name</TableHead>
<TableHead className="w-64">Description</TableHead>
<TableHead className="w-32">Status</TableHead>
<TableHead className="w-24 text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-28" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-40" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-16" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-16" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-32" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-36" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-20" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-12" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-24" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-44" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-14" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-20" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-28" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-32" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-18" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-14" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-20" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-40" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-16" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-16" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-30" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-38" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-18" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-10" />
</TableCell>
</TableRow>
<TableRow className="opacity-30 [&_[data-slot=skeleton]]:animate-none">
<TableCell>
<Skeleton className="h-4 w-26" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-34" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-22" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="ml-auto h-4 w-18" />
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>With Action Button
Use EmptyStateAction to add a call-to-action inside the empty state.
import {
Button,
EmptyState,
EmptyStateAction,
EmptyStateDescription,
EmptyStateHeading,
EmptyStateMedia,
EmptyStateTitle,
} from "@tilt-legal/cubitt-components/primitives";
import { File, Plus } from "@tilt-legal/cubitt-icons/ui/outline";
<EmptyState>
<EmptyStateMedia>
<File />
</EmptyStateMedia>
<EmptyStateHeading>
<EmptyStateTitle>No documents</EmptyStateTitle>
<EmptyStateDescription>
Upload or create your first document to get started.
</EmptyStateDescription>
</EmptyStateHeading>
<EmptyStateAction>
<Button onClick={() => undefined}>
<Plus />
Create Document
</Button>
</EmptyStateAction>
</EmptyState>Error State
Set error on EmptyState to apply error styling to EmptyStateMedia and EmptyStateTitle.
import {
Button,
EmptyState,
EmptyStateAction,
EmptyStateDescription,
EmptyStateHeading,
EmptyStateMedia,
EmptyStateTitle,
} from "@tilt-legal/cubitt-components/primitives";
import {
RefreshAnticlockwise,
TriangleWarning,
} from "@tilt-legal/cubitt-icons/ui/outline";
<EmptyState error>
<EmptyStateMedia>
<TriangleWarning />
</EmptyStateMedia>
<EmptyStateHeading>
<EmptyStateTitle>Something went wrong</EmptyStateTitle>
<EmptyStateDescription>
We couldn't load your data. Please try again.
</EmptyStateDescription>
</EmptyStateHeading>
<EmptyStateAction>
<Button onClick={() => undefined} variant="secondary">
<RefreshAnticlockwise />
Try Again
</Button>
</EmptyStateAction>
</EmptyState>API Reference
EmptyState
The root container component. By default it fills the available parent space with centered content and generous padding.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "ghost" | "overlay" | "default" | default renders the solid shell, ghost removes shell styling, and overlay positions the state absolutely with a gradient background |
error | boolean | false | Enables error styling for EmptyStateMedia and EmptyStateTitle |
className | string | - | Additional CSS classes. Use this to override the default fill, spacing, or surface styling |
children | React.ReactNode | - | Child components |
EmptyStateMedia
Container for the icon with a circular background. Automatically applies error styling when the parent has error.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "default" | "lg" | "default" | Controls the media circle and icon size |
className | string | - | Additional CSS classes |
children | React.ReactNode | - | Icon element |
EmptyStateHeading
Container for the title and description.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes |
children | React.ReactNode | - | Title and description |
EmptyStateTitle
The title text. Automatically applies error styling when the parent has error.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes |
children | React.ReactNode | - | Title content |
EmptyStateDescription
The description text.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes |
children | React.ReactNode | - | Description content |
EmptyStateAction
Container for action buttons.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes |
children | React.ReactNode | - | Button element(s) |
Design Guidelines
When to Use
- Empty lists, tables, or grids
- Search results with no matches
- Error states when data fails to load
- First-time user experiences
Best Practices
- Always provide a clear, actionable title
- Include a helpful description explaining what the user can do
- Use
variant="ghost"when a parent container already owns the surrounding shell - Use
variant="overlay"when content should stay mounted underneath - When appropriate, include an action button to help users resolve the empty state
- Use the error state for failed data loading, not for validation errors