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.

Installation#

With the CLI

$npx lanterncn add empty

This 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

  1. Install the dependencies

    npm install class-variance-authority
  2. Copy the source into your project

    components/ui/empty.tsx
    import * 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 };
  3. Update the import paths to match your project

    The source imports cn from @/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.