Empty
A component for displaying empty states with icons, titles, descriptions, and call-to-action buttons. Perfect for showing when no data is available or guiding users to take action.
Usage
Basic Empty State
A simple empty state with an icon, title, and description:
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from "@prisma/eclipse";
export function BasicEmpty() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-inbox"></i>
</EmptyMedia>
<EmptyTitle>No Data</EmptyTitle>
<EmptyDescription>No data found</EmptyDescription>
</EmptyHeader>
</Empty>
);
}Live Example:
Live Example:
With Call-to-Action
Add buttons or actions to guide users:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function EmptyWithAction() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-folder-open"></i>
</EmptyMedia>
<EmptyTitle>No Projects Yet</EmptyTitle>
<EmptyDescription>
You haven't created any projects yet. Get started by creating your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create Project</Button>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
You haven't created any projects yet. Get started by creating your first project.
With Multiple Actions
Include multiple buttons or links:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function EmptyMultipleActions() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-folder-open"></i>
</EmptyMedia>
<EmptyTitle>No Projects Yet</EmptyTitle>
<EmptyDescription>
You haven't created any projects yet. Get started by creating your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button>Create Project</Button>
<Button variant="default-weak">Import Project</Button>
</div>
<a href="#" className="text-sm underline underline-offset-4">
Learn More
</a>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
You haven't created any projects yet. Get started by creating your first project.
Outline Variant
Add a border to create an outlined empty state:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function OutlineEmpty() {
return (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-cloud"></i>
</EmptyMedia>
<EmptyTitle>Cloud Storage Empty</EmptyTitle>
<EmptyDescription>
Upload files to your cloud storage to access them anywhere.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Upload Files</Button>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
Upload files to your cloud storage to access them anywhere.
With Background
Add background colors or gradients:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function BackgroundEmpty() {
return (
<Empty className="bg-background-neutral">
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-bell"></i>
</EmptyMedia>
<EmptyTitle>No Notifications</EmptyTitle>
<EmptyDescription>
You're all caught up. New notifications will appear here.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="default-weak">Refresh</Button>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
Without Icon
Omit the icon for a simpler layout:
import {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function SimpleEmpty() {
return (
<Empty>
<EmptyHeader>
<EmptyTitle>No Results Found</EmptyTitle>
<EmptyDescription>
Try adjusting your search or filter to find what you're looking for.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="default-weak">Clear Filters</Button>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
Try adjusting your search or filter to find what you're looking for.
Search Empty State
With an input field for search:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Input,
} from "@prisma/eclipse";
export function SearchEmpty() {
return (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<i className="fa-duotone fa-regular fa-magnifying-glass"></i>
</EmptyMedia>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you're looking for doesn't exist. Try searching for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Input placeholder="Search..." className="max-w-xs" />
<a href="#" className="text-sm text-foreground-neutral-weak">
Need help? Contact support
</a>
</EmptyContent>
</Empty>
);
}Live Example:
Live Example:
The page you're looking for doesn't exist. Try searching for what you need below.
Large Icon Variant
Use larger icons for more prominent empty states:
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@prisma/eclipse";
export function LargeIconEmpty() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia>
<i className="fa-duotone fa-regular fa-users"></i>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>Invite your team to collaborate on this project.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Invite Members</Button>
</EmptyContent>
</Empty>
);
}Live Example:
API Reference
Empty
The main container component for the empty state. Wraps the EmptyHeader and EmptyContent components.
Props:
className- Additional CSS classes (string, optional)- All standard HTML div attributes
<Empty>
<EmptyHeader />
<EmptyContent />
</Empty>EmptyHeader
Container for the empty state header, including the media, title, and description.
Props:
className- Additional CSS classes (string, optional)- All standard HTML div attributes
<EmptyHeader>
<EmptyMedia />
<EmptyTitle />
<EmptyDescription />
</EmptyHeader>EmptyMedia
Displays the media element of the empty state, such as an icon, image, or avatar.
Props:
variant- Visual variant ("default" | "icon", default: "default")"default"- No background, transparent"icon"- Muted background with rounded corners, sized for icons
className- Additional CSS classes (string, optional)- All standard HTML div attributes
// Icon variant with background
<EmptyMedia variant="icon">
<Icon />
</EmptyMedia>
// Default variant for larger elements
<EmptyMedia>
<Icon className="size-16" />
</EmptyMedia>EmptyTitle
Displays the title of the empty state.
Props:
className- Additional CSS classes (string, optional)- All standard HTML div attributes
<EmptyTitle>No data</EmptyTitle>EmptyDescription
Displays the description text of the empty state. Supports links with automatic styling.
Props:
className- Additional CSS classes (string, optional)- All standard HTML p attributes
<EmptyDescription>
You do not have any notifications.
<a href="#">Learn more</a>
</EmptyDescription>EmptyContent
Container for action elements like buttons, inputs, or links in the empty state.
Props:
className- Additional CSS classes (string, optional)- All standard HTML div attributes
<EmptyContent>
<Button>Add Project</Button>
</EmptyContent>Features
- ✅ Flexible composition with semantic components
- ✅ Two media variants (default and icon)
- ✅ Responsive design with proper spacing
- ✅ Automatic link styling in descriptions
- ✅ Centered text alignment
- ✅ Balanced text layout for readability
- ✅ Dashed border container
- ✅ Supports custom backgrounds and borders
- ✅ Icon sizing optimization
- ✅ Fully typed with TypeScript
- ✅ Customizable with className props
Best Practices
- Use clear, action-oriented titles that describe the empty state
- Provide helpful descriptions that guide users on what to do next
- Always include at least one call-to-action button when possible
- Use appropriate icons that represent the context (e.g., folder for files, bell for notifications)
- Keep descriptions concise but informative
- Use the
iconvariant for small icons,defaultfor larger visuals - Consider adding a border or background for better visual separation
- Ensure empty states are accessible with proper semantic HTML
- Test empty states in different contexts (search, filters, first-time use)
- Provide multiple action options when relevant (create, import, learn more)
Accessibility
The Empty component follows accessibility best practices:
- Uses semantic HTML structure with proper div hierarchy
- Supports keyboard navigation for interactive elements
- Link styling with proper contrast ratios
- Underlined links for clarity
- Readable text sizes (text-sm)
- Proper spacing for touch targets
- Works with screen readers
- Maintains proper focus order
- All interactive elements are keyboard accessible
Common Use Cases
The Empty component is perfect for:
- No data states - When lists or tables have no items
- Search results - When searches return no matches
- Filtered views - When filters exclude all items
- Notifications - When there are no notifications
- First-time use - Onboarding new users to features
- File uploads - Empty file storage or folders
- Team invites - No team members yet
- 404 pages - Page not found errors
- Completed tasks - All items processed or cleared
- Settings - Features not yet configured
- Archives - Empty archived content
Styling
The Empty component uses design tokens and can be customized:
- Container: Dashed border, rounded corners, padding, centered content
- Header: Vertical layout with gap spacing
- Media (icon variant): Muted background, size-8, rounded-lg
- Media (default variant): Transparent background for custom sizing
- Title: Small font, medium weight, tracking-tight
- Description: Small relaxed line height, muted foreground color
- Links: Underlined with hover effects to primary color
- Content: Flexible column layout with gap spacing
Customize by passing className props:
<Empty className="border-2 border-primary bg-primary/5">
<EmptyHeader className="gap-4">
<EmptyMedia variant="icon" className="bg-primary/10">
<Icon className="text-primary" />
</EmptyMedia>
<EmptyTitle className="text-lg">Custom Styled</EmptyTitle>
</EmptyHeader>
</Empty>Design Patterns
Progressive Disclosure:
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<Icon />
</EmptyMedia>
<EmptyTitle>Get Started</EmptyTitle>
<EmptyDescription>Create your first item to begin.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Quick Start</Button>
<details className="text-sm text-foreground-neutral-weak">
<summary className="cursor-pointer">Advanced options</summary>
<div className="mt-2">Additional setup information...</div>
</details>
</EmptyContent>
</Empty>Error Recovery:
<Empty className="border border-stroke-error">
<EmptyHeader>
<EmptyMedia variant="icon" className="bg-background-error text-foreground-error">
<AlertCircle />
</EmptyMedia>
<EmptyTitle>Connection Error</EmptyTitle>
<EmptyDescription>Unable to load data. Check your connection and try again.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button onClick={retry}>Retry</Button>
</EmptyContent>
</Empty>Loading to Empty Transition:
{
isLoading ? (
<Spinner />
) : items.length === 0 ? (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<Icon />
</EmptyMedia>
<EmptyTitle>No Items</EmptyTitle>
<EmptyDescription>Add your first item to get started.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Add Item</Button>
</EmptyContent>
</Empty>
) : (
<ItemsList items={items} />
);
}