Components
Use the Empty component to display an empty state.
Playground
EmptyHeader — EmptyMedia, EmptyTitle and EmptyDescription — with an EmptyContent block for actions. The variant="icon" media renders a muted tile sized for a single icon.import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@workspace/ui/components/empty"
import { Button } from "@workspace/ui/components/button"
import { FolderPlusIcon } from "@phosphor-icons/react"
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderPlusIcon />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven't created any projects yet. Get started by creating
your first one.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button size="sm">Create project</Button>
<Button variant="outline" size="sm">
Import
</Button>
</div>
</EmptyContent>
</Empty>border-dashed — adding the border class is enough to draw the classic dashed drop-zone outline.<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<CloudArrowUpIcon />
</EmptyMedia>
<EmptyTitle>Cloud storage is empty</EmptyTitle>
<EmptyDescription>
Upload files to your cloud storage to access them anywhere.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
Upload files
</Button>
</EmptyContent>
</Empty>bg-muted/30 wash (or any token surface) when the empty state needs to read as a distinct region.<Empty className="border bg-muted/30">
<EmptyHeader>
<EmptyMedia variant="icon">
<BellSlashIcon />
</EmptyMedia>
<EmptyTitle>No notifications</EmptyTitle>
<EmptyDescription>
You're all caught up. New notifications will appear here.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
Notification settings
</Button>
</EmptyContent>
</Empty>EmptyMedia variant keeps a transparent background — drop in an Avatar when the state is about a person rather than data.import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@workspace/ui/components/avatar"
<Empty>
<EmptyHeader>
<EmptyMedia variant="default">
<Avatar className="size-12">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</EmptyMedia>
<EmptyTitle>@shadcn is offline</EmptyTitle>
<EmptyDescription>
They will receive your message when they come back online.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Leave a message</Button>
</EmptyContent>
</Empty>AvatarGroup works well for collaboration-shaped empty states like an unstaffed project.import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@workspace/ui/components/avatar"
import { UserPlusIcon } from "@phosphor-icons/react"
<Empty>
<EmptyHeader>
<EmptyMedia>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/vercel.png" alt="@vercel" />
<AvatarFallback>VC</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>No team members</EmptyTitle>
<EmptyDescription>
Invite your team to start collaborating on this project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">
<UserPlusIcon />
Invite members
</Button>
</EmptyContent>
</Empty>EmptyContent takes any control — pair it with an InputGroup for search-your-way-out states like a 404 page. Links inside EmptyDescription are underlined and tint to --primary on hover.import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@workspace/ui/components/input-group"
import { Kbd } from "@workspace/ui/components/kbd"
import { MagnifyingGlassIcon } from "@phosphor-icons/react"
<Empty>
<EmptyHeader>
<EmptyTitle>404 — Page not found</EmptyTitle>
<EmptyDescription>
The page you're looking for doesn't exist. Try searching instead.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="sm:w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<MagnifyingGlassIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>dir="rtl" on a wrapper or inherit it from a Direction provider — the column layout is symmetric, so text alignment and action order follow the direction automatically.<div dir="rtl">
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderPlusIcon />
</EmptyMedia>
<EmptyTitle>لا توجد مشاريع بعد</EmptyTitle>
<EmptyDescription>
لم تنشئ أي مشروع بعد. ابدأ بإنشاء مشروعك الأول.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">إنشاء مشروع</Button>
</EmptyContent>
</Empty>
</div>div and accepts all div props — only EmptyMedia adds a prop of its own.| Prop | Type | Default |
|---|---|---|
| EmptyMedia variant | "default" | "icon" | "default" |
| className (all parts) | string | — |
Tokens used
10 design tokens consumed by the Empty component.
--muted
Muted
--foreground
Foreground
--muted-foreground
Muted Foreground
--primary
Primary
--radius-lg
Radius 1× (10px)
--font-heading
GT Alpina Condensed
--text-lg
Text Large (18px)
--text-sm
Text Small (14px)
--font-weight-medium
Font weight Medium (500)
--spacing
Spacing unit (4px)