Empty state
Empty, no-results, error, offline, maintenance and rate-limit states with one API.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/empty-state.jsonNothing here yet
Create your first project to start tracking metrics.
components/examples/empty-state-demo.tsx
"use client";
import * as React from "react";
import { Plus, RotateCcw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { EmptyState } from "@/components/ui/empty-state";
import { SegmentedControl } from "@/components/ui/segmented-control";
const variants = ["empty", "search", "error", "offline", "rate-limit"] as const;
export default function EmptyStateDemo() {
const [v, setV] = React.useState<(typeof variants)[number]>("empty");
const copy = {
empty: { d: "Create your first project to start tracking metrics.", a: <Button size="sm"><Plus /> New project</Button> },
search: { d: "No projects match “chekout”. Check the spelling or clear filters.", a: <Button size="sm" variant="outline">Clear filters</Button> },
error: { d: "We couldn't load your projects. It's on us, not you.", a: <Button size="sm" variant="outline"><RotateCcw /> Retry</Button> },
offline: { d: "Changes are saved locally and will sync when you reconnect.", a: null },
"rate-limit": { d: "You've hit 100 requests a minute. Try again in 30 seconds.", a: null },
}[v];
return (
<div className="w-full max-w-md space-y-4">
<SegmentedControl size="sm" className="flex-wrap" aria-label="Variant" value={v} onValueChange={setV} options={variants.map((x) => ({ value: x, label: x }))} />
<EmptyState bordered variant={v} description={copy.d} action={copy.a} />
</div>
);
}When to use
- Empty lists, no search results, errors, offline, no access and maintenance.
When not to use
- Loading: use a skeleton.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/empty-state.json1Install the dependencies
npm install lucide-react2Copy the source into your project
components/ui/empty-state.tsximport * as React from "react"; import { CloudOff, FileQuestion, Inbox, ServerCrash, ShieldAlert, Timer, Wrench } from "lucide-react"; import { cn } from "@/lib/utils"; const presets = { empty: { icon: <Inbox />, title: "Nothing here yet" }, search: { icon: <FileQuestion />, title: "No results" }, error: { icon: <ServerCrash />, title: "Something went wrong" }, offline: { icon: <CloudOff />, title: "You're offline" }, forbidden: { icon: <ShieldAlert />, title: "You don't have access" }, maintenance: { icon: <Wrench />, title: "Down for maintenance" }, "rate-limit": { icon: <Timer />, title: "Too many requests" }, } as const; export interface EmptyStateProps { variant?: keyof typeof presets; icon?: React.ReactNode; title?: React.ReactNode; description?: React.ReactNode; action?: React.ReactNode; /** Draw a dashed border around it, for empty panels. */ bordered?: boolean; className?: string; } /** Empty, error, offline, maintenance and rate-limit states with one API. */ export function EmptyState({ variant = "empty", icon, title, description, action, bordered, className }: EmptyStateProps) { const p = presets[variant]; return ( <div role={variant === "error" || variant === "offline" ? "alert" : undefined} className={cn("flex flex-col items-center justify-center px-6 py-12 text-center", bordered && "rounded-lg border border-dashed border-border", className)} > <span className={cn("mb-4 grid size-11 place-items-center rounded-lg border border-border bg-card text-muted-foreground shadow-elev-1 [&_svg]:size-5", variant === "error" && "text-destructive")}> {icon ?? p.icon} </span> <p className="font-medium">{title ?? p.title}</p> {description && <p className="mt-1 max-w-sm text-sm text-muted-foreground">{description}</p>} {action && <div className="mt-5 flex flex-wrap justify-center gap-2">{action}</div>} </div> ); }
Usage
Import it from @/components/ui/empty-state after installing.
example.tsx
import { EmptyState } from "@/components/ui/empty-state";
import { Button } from "@/components/ui/button";
export function NoProjects() {
return <EmptyState bordered title="No projects yet" description="Create one to start deploying." action={<Button>New project</Button>} />;
}API reference
EmptyState
Icon, title, description and action, with presets for common cases.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "empty" | "search" | "error" | "offline" | "forbidden" | "maintenance" | "empty" | Sets a default icon and title. |
| icon / title / description / action | ReactNode | – | Override any part. |
| bordered | boolean | – | Dashed border for empty panels. |
Accessibility
- The title is a heading-sized paragraph; the icon is decorative.
Responsive behavior
- Centered with a narrow text column.
Performance
- Static.
Notes for AI
- Always give people a next step in action.
This page as Markdown, for assistants and coding agents:
/components/empty-state/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt