Empty
Empty-state layout with media, title, description, and action slots.
Examples
Avatar
Use the EmptyMedia component to display an avatar in the empty state.
This user is currently offline. You can leave a message to notify them or try again later.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
export function EmptyAvatar() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="default">
<Avatar className="size-12">
<AvatarImage src="https://github.com/codefastlabs.png" className="grayscale" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
</EmptyMedia>
<EmptyTitle>User Offline</EmptyTitle>
<EmptyDescription>
This user is currently offline. You can leave a message to notify them or try again later.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Leave Message</Button>
</EmptyContent>
</Empty>
);
}
Avatar Group
Use the EmptyMedia component to display an avatar group in the empty state.
Invite your team to collaborate on this project.
import { Avatar, AvatarFallback, AvatarImage } from "@codefast/ui/avatar";
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { PlusIcon } from "lucide-react";
export function EmptyAvatarGroup() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia>
<div className="flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<Avatar>
<AvatarImage src="https://github.com/codefastlabs.png" alt="@codefast" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=128&h=128&fit=crop&q=80"
alt="@leo"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=128&h=128&fit=crop&q=80"
alt="@ava"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>Invite your team to collaborate on this project.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">
<PlusIcon />
Invite Members
</Button>
</EmptyContent>
</Empty>
);
}
Background
Use the bg-* and bg-gradient-* utilities to add a background to the empty state.
You're all caught up. New notifications will appear here.
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { BellIcon, RefreshCcwIcon } from "lucide-react";
export function EmptyMuted() {
return (
<Empty className="h-full bg-muted/30">
<EmptyHeader>
<EmptyMedia variant="icon">
<BellIcon />
</EmptyMedia>
<EmptyTitle>No Notifications</EmptyTitle>
<EmptyDescription className="max-w-xs text-pretty">
You're all caught up. New notifications will appear here.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">
<RefreshCcwIcon />
Refresh
</Button>
</EmptyContent>
</Empty>
);
}
Card
Wrap the Empty component in a Card to create an empty-state card.
You haven't created any projects yet. Get started by creating your first project.
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { ArrowUpRightIcon, FolderIcon } from "lucide-react";
export function EmptyInCard() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderIcon />
</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 asChild>
<a href="/">Create project</a>
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button variant="link" asChild className="text-muted-foreground">
<a href="/">
Learn more <ArrowUpRightIcon />
</a>
</Button>
</EmptyContent>
</Empty>
);
}
InputGroup
You can add an InputGroup component to the EmptyContent component.
The page you're looking for doesn't exist. Try searching for what you need below.
Need help? Contact support
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyTitle } from "@codefast/ui/empty";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { Kbd } from "@codefast/ui/kbd";
import { SearchIcon } from "lucide-react";
export function EmptyInputGroup() {
return (
<Empty>
<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="sm:w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="/">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
);
}
Outline
Use the border utility class to create an outline empty state.
Upload files to your cloud storage to access them anywhere.
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { CloudIcon } from "lucide-react";
export function EmptyOutline() {
return (
<Empty className="border border-dashed">
<EmptyHeader>
<EmptyMedia variant="icon">
<CloudIcon />
</EmptyMedia>
<EmptyTitle>Cloud Storage 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>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
لم تقم بإنشاء أي مشاريع بعد. ابدأ بإنشاء مشروعك الأول.
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { ArrowUpRightIcon, FolderCodeIcon } from "lucide-react";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {
title: "No Projects Yet",
description: "You haven't created any projects yet. Get started by creating your first project.",
createProject: "Create Project",
importProject: "Import Project",
learnMore: "Learn More",
},
},
ar: {
dir: "rtl",
values: {
title: "لا توجد مشاريع بعد",
description: "لم تقم بإنشاء أي مشاريع بعد. ابدأ بإنشاء مشروعك الأول.",
createProject: "إنشاء مشروع",
importProject: "استيراد مشروع",
learnMore: "تعرف على المزيد",
},
},
he: {
dir: "rtl",
values: {
title: "אין פרויקטים עדיין",
description: "עדיין לא יצרת פרויקטים. התחל על ידי יצירת הפרויקט הראשון שלך.",
createProject: "צור פרויקט",
importProject: "ייבא פרויקט",
learnMore: "למד עוד",
},
},
};
export function EmptyRtl() {
const { dir, t } = useTranslation(translations, "ar");
return (
<Empty dir={dir}>
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderCodeIcon />
</EmptyMedia>
<EmptyTitle>{t.title}</EmptyTitle>
<EmptyDescription>{t.description}</EmptyDescription>
</EmptyHeader>
<EmptyContent className="flex-row justify-center gap-2">
<Button>{t.createProject}</Button>
<Button variant="outline">{t.importProject}</Button>
</EmptyContent>
<Button variant="link" asChild className="text-muted-foreground" size="sm">
<a href="/">
{t.learnMore} <ArrowUpRightIcon className="rtl:rotate-270" data-icon="inline-end" />
</a>
</Button>
</Empty>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- border-dashed is already baked into Empty's default styling — add a border (width) utility yourself to actually render the dashed outline.
- Composes with Card (wrap Empty in a Card) or an InputGroup inside EmptyContent for empty-search patterns.
API reference
Props for each part of the component. All native element props are also forwarded.
Empty
Layout wrapper for an empty/zero state.
childrenReactNodeCompose EmptyHeader (media, title, description) and EmptyContent (action).
EmptyMedia
Icon or illustration slot above the title.
variant"default" | "icon"Render as plain media, or "icon" to frame it in a rounded, muted background.
Default
"default"childrenReactNodeThe icon or illustration itself.
EmptyTitle
Headline for the empty state.
childrenReactNodeThe headline text.
EmptyDescription
Supporting text under the title.
childrenReactNodeThe supporting copy.
EmptyContent
Action slot below the header.
childrenReactNodeButtons, links, or other calls to action.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
- Use a real heading for EmptyTitle so it fits the document outline.
- Always give users a next step — a button or link out of the empty state.
- Keep the icon decorative; the meaning lives in the title and description.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Explain why it’s empty and what to do next.
- Offer a single, primary action.
Don’t
- Don’t leave a blank area with no guidance.
- Don’t pile on multiple competing actions.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.