Empty
A container for displaying empty state information. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { BookIcon, RouteIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export default function Particle() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<RouteIcon />
</EmptyMedia>
<EmptyTitle>No upcoming meetings</EmptyTitle>
<EmptyDescription>Create a meeting to get started.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button size="sm">Create meeting</Button>
<Button size="sm" variant="outline">
<BookIcon />
View docs
</Button>
</div>
</EmptyContent>
</Empty>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/empty
Usage
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<Icon />
</EmptyMedia>
<EmptyTitle>No data</EmptyTitle>
<EmptyDescription>No data found</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Add data</Button>
</EmptyContent>
</Empty>Examples
No Results
A search-icon illustration with a "No results" title and description — shown when a query returns zero matches.
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Empty className="bg-muted">
<EmptyHeader>
<EmptyTitle>No results found</EmptyTitle>
<EmptyDescription>
No results found for your search. Try adjusting your search terms.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Try again</Button>
<Button
className="text-muted-foreground"
render={<Link href="#" />}
variant="link"
>
Learn more <ArrowUpRightIcon />
</Button>
</EmptyContent>
</Empty>
</div>
);
}
With Search
Combines the empty message with an inline search input so users can refine their query without leaving the view.
import { CircleDashedIcon } from "lucide-react";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/components/ui/empty";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Kbd } from "@/components/ui/kbd";
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Empty className="border">
<EmptyHeader>
<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>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages…" />
<InputGroupAddon>
<CircleDashedIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
</div>
);
}
With Add Button
A "No items yet" state with a prominent create button, guiding users to populate the list for the first time.
import { FolderIcon, PlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderIcon />
</EmptyMedia>
<EmptyTitle>Nothing to see here</EmptyTitle>
<EmptyDescription>
No posts have been created yet. Get started by{" "}
<a href="#">creating your first post</a>.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">
<PlusIcon data-icon="inline-start" />
New Post
</Button>
</EmptyContent>
</Empty>
</div>
);
}
Upload Drop Zone
A dashed-border drop zone with a cloud-upload icon and "Drag & drop or click to browse" instructions.
import { CloudUploadIcon, PlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="w-full max-w-md border border-dashed py-12">
<EmptyHeader>
<EmptyMedia variant="icon">
<CloudUploadIcon />
</EmptyMedia>
<EmptyTitle>Upload files</EmptyTitle>
<EmptyDescription>
Drag and drop files here, or click to browse.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm" variant="outline">
<PlusIcon data-icon="inline-start" />
Browse Files
</Button>
<EmptyDescription className="text-xs">
PNG, JPG, SVG up to 10MB
</EmptyDescription>
</EmptyContent>
</Empty>
</div>
);
}
No Automations
Features a decorative toggle illustration and two CTA buttons — one primary and one secondary — to create or learn about automations.
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
function AutomationIllustration() {
return (
<svg
aria-hidden="true"
fill="none"
height="120"
viewBox="0 0 200 120"
width="200"
xmlns="http://www.w3.org/2000/svg"
>
{/* Left connection line with arrow */}
<path
className="stroke-muted-foreground/30"
d="M30 60 L68 60"
markerEnd="url(#arrowhead)"
strokeLinecap="round"
strokeWidth="2"
/>
<polygon
className="fill-muted-foreground/30"
points="66,56 74,60 66,64"
/>
{/* Toggle body */}
<rect
className="fill-primary/5 stroke-primary/60 dark:fill-primary/10"
height="36"
rx="18"
strokeWidth="2"
width="56"
x="76"
y="42"
/>
{/* Toggle circle */}
<circle className="fill-primary/40" cx="94" cy="60" r="12" />
<circle className="fill-primary" cx="94" cy="60" r="6" />
{/* Right connection line */}
<path
className="stroke-muted-foreground/30"
d="M134 60 Q150 60 158 48"
fill="none"
strokeLinecap="round"
strokeWidth="2"
/>
<circle className="fill-muted-foreground/20" cx="162" cy="44" r="3" />
{/* Bottom right connection */}
<path
className="stroke-muted-foreground/30"
d="M134 60 Q150 60 158 72"
fill="none"
strokeLinecap="round"
strokeWidth="2"
/>
<circle className="fill-muted-foreground/20" cx="162" cy="76" r="3" />
{/* Decorative dots */}
<circle className="fill-muted-foreground/20" cx="22" cy="60" r="2" />
<circle className="fill-muted-foreground/15" cx="174" cy="44" r="2" />
<circle className="fill-muted-foreground/15" cx="174" cy="76" r="2" />
</svg>
);
}
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="py-12">
<EmptyHeader>
<EmptyMedia>
<AutomationIllustration />
</EmptyMedia>
<EmptyTitle>No automations yet</EmptyTitle>
<EmptyDescription>
Hook up your favorite tools and let the automation magic begin.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create new automation</Button>
</EmptyContent>
</Empty>
</div>
);
}
No Products
A layered depth effect using stacked translucent cards with a blur, conveying "nothing here yet" in a visually engaging way.
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
function StackedCardsIllustration() {
return (
<div aria-hidden="true" className="relative h-24 w-52">
{/* Back card */}
<div className="absolute inset-x-6 top-0 h-6 rounded-t-lg border border-border/50 bg-muted/60 dark:bg-muted/30" />
{/* Middle card */}
<div className="absolute inset-x-3 top-3 h-6 rounded-t-lg border border-border/60 bg-muted/80 dark:bg-muted/50" />
{/* Front card */}
<div className="absolute inset-x-0 top-6 flex h-16 items-center gap-3 rounded-lg border border-border bg-background px-4 shadow-sm">
<div className="size-8 shrink-0 rounded bg-muted" />
<div className="flex flex-1 flex-col gap-1.5">
<div className="h-2.5 w-3/4 rounded bg-muted" />
<div className="h-2 w-1/2 rounded bg-muted/60" />
</div>
</div>
{/* Fade overlay */}
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-8 bg-linear-to-b from-background/0 via-background/60 to-background" />
</div>
);
}
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="py-12">
<EmptyHeader>
<EmptyMedia>
<StackedCardsIllustration />
</EmptyMedia>
<EmptyTitle>No products</EmptyTitle>
<EmptyDescription>
No data here yet. We will notify you when there's an update.
</EmptyDescription>
</EmptyHeader>
</Empty>
</div>
);
}
No Payments
A credit-card illustration with a "No payments yet" message and a link to documentation or the billing setup flow.
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
function CreditCardIllustration() {
return (
<svg
aria-hidden="true"
fill="none"
height="130"
viewBox="0 0 200 130"
width="200"
xmlns="http://www.w3.org/2000/svg"
>
{/* Shadow card behind */}
<rect
className="fill-muted/50 dark:fill-muted/25"
height="78"
rx="10"
transform="rotate(6 108 51)"
width="120"
x="48"
y="12"
/>
{/* Main card */}
<rect
className="fill-background stroke-border"
height="82"
rx="10"
strokeWidth="1.5"
width="128"
x="36"
y="18"
/>
{/* Chip */}
<rect
className="fill-muted-foreground/15 stroke-muted-foreground/20"
height="16"
rx="3"
strokeWidth="1"
width="22"
x="52"
y="38"
/>
{/* Chip lines */}
<line
className="stroke-muted-foreground/15"
strokeWidth="0.8"
x1="52"
x2="74"
y1="46"
y2="46"
/>
<line
className="stroke-muted-foreground/15"
strokeWidth="0.8"
x1="63"
x2="63"
y1="38"
y2="54"
/>
{/* Contactless icon */}
<g className="stroke-muted-foreground/20" fill="none" strokeWidth="1.5">
<path d="M84 42 Q87 46 84 50" strokeLinecap="round" />
<path d="M88 40 Q92 46 88 52" strokeLinecap="round" />
<path d="M92 38 Q97 46 92 54" strokeLinecap="round" />
</g>
{/* Card number dots */}
<g className="fill-muted-foreground/20">
<circle cx="56" cy="68" r="2" />
<circle cx="64" cy="68" r="2" />
<circle cx="72" cy="68" r="2" />
<circle cx="80" cy="68" r="2" />
</g>
<g className="fill-muted-foreground/15">
<circle cx="94" cy="68" r="2" />
<circle cx="102" cy="68" r="2" />
<circle cx="110" cy="68" r="2" />
<circle cx="118" cy="68" r="2" />
</g>
{/* Bottom info lines */}
<rect
className="fill-muted-foreground/12"
height="3"
rx="1.5"
width="40"
x="52"
y="80"
/>
<rect
className="fill-muted-foreground/12"
height="3"
rx="1.5"
width="28"
x="120"
y="80"
/>
{/* Card network logo placeholder */}
<circle className="fill-muted-foreground/8" cx="140" cy="88" r="6" />
<circle className="fill-muted-foreground/12" cx="150" cy="88" r="6" />
{/* Floating decorative elements */}
<circle className="fill-primary/10" cx="26" cy="50" r="3" />s
<circle className="fill-primary/10" cx="180" cy="40" r="2" />
<path
className="fill-muted-foreground/10"
d="M174 70 L178 66 L178 74 Z"
/>
</svg>
);
}
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="py-12">
<EmptyHeader>
<EmptyMedia>
<CreditCardIllustration />
</EmptyMedia>
<EmptyTitle>No payment methods</EmptyTitle>
<EmptyDescription>
Add a payment method to start making transactions securely.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Add payment method</Button>
</EmptyContent>
</Empty>
</div>
);
}
No Events
A calendar graphic paired with a "No upcoming events" message and a schedule action button.
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
function CalendarIllustration() {
return (
<svg
aria-hidden="true"
fill="none"
height="140"
viewBox="0 0 160 140"
width="160"
xmlns="http://www.w3.org/2000/svg"
>
{/* Calendar body */}
<rect
className="fill-background stroke-border"
height="96"
rx="10"
strokeWidth="1.5"
width="112"
x="24"
y="28"
/>
{/* Calendar header bar */}
<rect
className="fill-muted dark:fill-muted/60"
height="24"
rx="10"
width="112"
x="24"
y="28"
/>
<rect
className="fill-muted dark:fill-muted/60"
height="10"
width="112"
x="24"
y="42"
/>
{/* Calendar hooks */}
<line
className="stroke-muted-foreground/30"
strokeLinecap="round"
strokeWidth="3"
x1="56"
x2="56"
y1="20"
y2="36"
/>
<line
className="stroke-muted-foreground/30"
strokeLinecap="round"
strokeWidth="3"
x1="104"
x2="104"
y1="20"
y2="36"
/>
{/* Day dots - row 1 */}
<circle className="fill-muted-foreground/10" cx="48" cy="68" r="4" />
<circle className="fill-muted-foreground/10" cx="68" cy="68" r="4" />
<circle className="fill-muted-foreground/10" cx="88" cy="68" r="4" />
<circle className="fill-muted-foreground/10" cx="108" cy="68" r="4" />
{/* Day dots - row 2 */}
<circle className="fill-muted-foreground/10" cx="48" cy="86" r="4" />
<circle className="fill-muted-foreground/10" cx="68" cy="86" r="4" />
<circle className="fill-primary/25" cx="88" cy="86" r="4" />
<circle className="fill-primary" cx="88" cy="86" r="2" />
<circle className="fill-muted-foreground/10" cx="108" cy="86" r="4" />
{/* Day dots - row 3 */}
<circle className="fill-muted-foreground/10" cx="48" cy="104" r="4" />
<circle className="fill-muted-foreground/10" cx="68" cy="104" r="4" />
<circle className="fill-muted-foreground/10" cx="88" cy="104" r="4" />
<circle className="fill-muted-foreground/10" cx="108" cy="104" r="4" />
{/* Floating decoration */}
<circle className="fill-muted-foreground/10" cx="14" cy="70" r="2" />
<circle className="fill-primary/10" cx="148" cy="56" r="2.5" />
<circle className="fill-muted-foreground/8" cx="146" cy="100" r="1.5" />
</svg>
);
}
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="py-12">
<EmptyHeader>
<EmptyMedia>
<CalendarIllustration />
</EmptyMedia>
<EmptyTitle>No upcoming events</EmptyTitle>
<EmptyDescription>
Your schedule is clear. Create an event to get started.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create event</Button>
</EmptyContent>
</Empty>
</div>
);
}
All Caught Up
A bell icon with an "All caught up" headline used when the user has no unread notifications, paired with a manage preferences link.
import { BellIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<BellIcon />
</EmptyMedia>
<EmptyTitle>All caught up</EmptyTitle>
<EmptyDescription>
No new notifications. We'll let you know when something
arrives.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm" variant="outline">
Manage preferences
</Button>
</EmptyContent>
</Empty>
</div>
);
}
Invite Team
A user-plus icon with an inline email invite form — ideal for empty team or collaborator lists where the first action is sending an invitation.
import { UserPlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="w-full max-w-sm border">
<EmptyHeader>
<EmptyMedia variant="icon">
<UserPlusIcon />
</EmptyMedia>
<EmptyTitle>No team members yet</EmptyTitle>
<EmptyDescription>
Invite your team to collaborate and get more done together.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-full">
<InputGroupInput
aria-label="Email address"
placeholder="colleague@company.com"
type="email"
/>
<InputGroupAddon align="inline-end">
<Button size="sm" variant="ghost">
Invite
</Button>
</InputGroupAddon>
</InputGroup>
</EmptyContent>
</Empty>
</div>
);
}
No Matching Results
A filter-clear icon with a "No matching results" message and buttons to clear or adjust active filters.
import { FilterXIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<FilterXIcon />
</EmptyMedia>
<EmptyTitle>No matching results</EmptyTitle>
<EmptyDescription>
Your current filters returned no results. Try adjusting or clearing
them.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button size="sm">Clear filters</Button>
<Button size="sm" variant="outline">
Edit filters
</Button>
</div>
</EmptyContent>
</Empty>
</div>
);
}
Inbox Zero
An SVG inbox illustration celebrating an empty inbox, with a compose action to prompt the user to start a conversation.
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
function InboxIllustration() {
return (
<svg
aria-hidden="true"
fill="none"
height="120"
viewBox="0 0 180 120"
width="180"
xmlns="http://www.w3.org/2000/svg"
>
{/* Inbox tray */}
<rect
className="fill-background stroke-border"
height="72"
rx="8"
strokeWidth="1.5"
width="120"
x="30"
y="28"
/>
{/* Tray bottom shelf */}
<rect
className="fill-muted dark:fill-muted/50"
height="20"
rx="0"
width="120"
x="30"
y="80"
/>
<rect
className="fill-muted dark:fill-muted/50"
height="8"
rx="0"
width="120"
x="30"
y="92"
/>
<path
className="fill-muted dark:fill-muted/50"
d="M30 92 Q30 100 38 100 H142 Q150 100 150 92"
/>
{/* Envelope 1 - back */}
<rect
className="fill-muted/60 stroke-border/60"
height="30"
rx="4"
strokeWidth="1"
width="80"
x="50"
y="38"
/>
<path
className="stroke-border/40"
d="M50 42 L90 58 L130 42"
fill="none"
strokeWidth="1"
/>
{/* Envelope 2 - front */}
<rect
className="fill-background stroke-border"
height="30"
rx="4"
strokeWidth="1.5"
width="80"
x="50"
y="46"
/>
<path
className="stroke-muted-foreground/30"
d="M50 50 L90 66 L130 50"
fill="none"
strokeWidth="1.5"
/>
{/* Decorative dots */}
<circle className="fill-primary/15" cx="22" cy="60" r="3" />
<circle className="fill-muted-foreground/10" cx="160" cy="44" r="2" />
<circle className="fill-muted-foreground/10" cx="164" cy="80" r="2.5" />
</svg>
);
}
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="py-12">
<EmptyHeader>
<EmptyMedia>
<InboxIllustration />
</EmptyMedia>
<EmptyTitle>Inbox zero</EmptyTitle>
<EmptyDescription>
You're all caught up. No new messages waiting for you.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">Compose message</Button>
</EmptyContent>
</Empty>
</div>
);
}
Access Restricted
A lock icon on a muted background for permission-gated views, with request-access and go-back actions.
import { LockIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export function Pattern() {
return (
<div className="flex items-center justify-center p-4">
<Empty className="border bg-muted/40">
<EmptyHeader>
<EmptyMedia variant="icon">
<LockIcon />
</EmptyMedia>
<EmptyTitle>Access restricted</EmptyTitle>
<EmptyDescription>
You don't have permission to view this content. Contact your
admin to request access.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button size="sm">Request access</Button>
<Button size="sm" variant="outline">
Go back
</Button>
</div>
</EmptyContent>
</Empty>
</div>
);
}

