Feedback
Empty.
An empty state with an icon in a grid-patterned square, a display title that ends in an orange period, a short description and actions.
No turtles yet
Place a turtle and run the pairing program to see its fuel, position and job here.
import { Bot, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
export default function EmptyDemo() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia>
<Bot />
</EmptyMedia>
<EmptyTitle>No turtles yet</EmptyTitle>
<EmptyDescription>
Place a turtle and run the pairing program to see its fuel, position and job here.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex flex-wrap justify-center gap-2">
<Button>
<Plus />
Pair a turtle
</Button>
<Button variant="secondary">Read the guide</Button>
</div>
</EmptyContent>
</Empty>
);
}Installation#
With the CLI
$npx lanterncn add emptyThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/empty. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install class-variance-authorityCopy the source into your project
components/ui/empty.tsximport * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; function Empty({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="empty" className={cn( "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border border-dashed border-input p-6 text-center text-balance md:p-12", className, )} {...props} /> ); } function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="empty-header" className={cn("flex max-w-sm flex-col items-center gap-2", className)} {...props} /> ); } const emptyMediaVariants = cva( "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { default: "bg-transparent", icon: "size-16 rounded-md border bg-accent bg-grid text-[#b7ca9e] [&_svg:not([class*='size-'])]:size-7 [&_svg]:stroke-[1.5]", }, }, defaultVariants: { variant: "icon" }, }, ); function EmptyMedia({ className, variant = "icon", ...props }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) { return ( <div data-slot="empty-media" data-variant={variant} className={cn(emptyMediaVariants({ variant, className }))} {...props} /> ); } /** Display title. An orange period is added after the text, so leave it off. */ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="empty-title" className={cn( "font-display text-2xl leading-tight font-medium tracking-tight after:text-primary after:content-['.']", className, )} {...props} /> ); } function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { return ( <p data-slot="empty-description" className={cn( "text-sm/relaxed text-muted-foreground [&>a]:text-primary [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-[#f8b981]", className, )} {...props} /> ); } function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="empty-content" className={cn("flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance", className)} {...props} /> ); } export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
} from "@/components/ui/empty";<Empty>
<EmptyHeader>
<EmptyMedia>
<Bot />
</EmptyMedia>
<EmptyTitle>No turtles yet</EmptyTitle>
<EmptyDescription>Pair one to see it here.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Pair a turtle</Button>
</EmptyContent>
</Empty>EmptyTitle adds the orange period itself, so write the title without one.