For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Empty

The Empty component from this design system, built on Base UI.

Installation

$ pnpm dlx shadcn@latest add @cobalt/empty

Options

  • The default Empty is a recessed, borderless surface with a 12px radius, the same fill as Card variant="muted". It stays visible on a card or a page and disappears into a column that is already recessed.
  • EmptyMedia variant="icon" is a 40px raised chip (--surface-card with the card shadow), so it stands out from the recessed surface.
  • variant="ghost" removes the surface and the minimum height, for use on coloured surfaces. The icon chip falls back to the recessed fill there.

Source

import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "cn";

function Empty({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & {
  /** `ghost` has no surface and no minimum height, for use on coloured surfaces. */
  variant?: "default" | "ghost";
}) {
  return (
    <div
      data-slot="empty"
      data-variant={variant}
      className={cn(
        "group/empty flex min-h-64 w-full min-w-0 flex-1 flex-col items-center justify-center gap-2 rounded-[12px] bg-[var(--surface-sunken,var(--muted))] px-6 text-center text-balance",
        variant === "ghost" && "min-h-0 bg-transparent",
        className
      )}
      {...props}
    />
  );
}

function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-header"
      className={cn("flex max-w-64 flex-col items-center gap-2", className)}
      {...props}
    />
  );
}

const emptyMediaVariants = cva(
  "flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
  {
    defaultVariants: {
      variant: "default",
    },
    variants: {
      variant: {
        default: "bg-transparent",
        icon: "flex size-10 shrink-0 items-center justify-center rounded-full bg-[var(--surface-card,var(--background))] text-muted-foreground shadow-[0_1px_1px_0_rgb(0_0_0/0.05)] group-data-[variant=ghost]/empty:bg-[var(--surface-sunken,var(--muted))] group-data-[variant=ghost]/empty:shadow-none dark:shadow-[0_1px_1px_0_rgb(0_0_0/0.14)] [&_svg:not([class*='size-'])]:size-5",
      },
    },
  }
);

function EmptyMedia({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
  return (
    <div
      data-slot="empty-icon"
      data-variant={variant}
      className={cn(emptyMediaVariants({ className, variant }))}
      {...props}
    />
  );
}

function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-title"
      className={cn("text-sm/5 font-medium text-foreground", className)}
      {...props}
    />
  );
}

function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <div
      data-slot="empty-description"
      className={cn(
        "text-sm/5 text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
        className
      )}
      {...props}
    />
  );
}

function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-content"
      className={cn(
        "flex w-full max-w-64 min-w-0 flex-col items-center gap-2 text-sm text-balance",
        className
      )}
      {...props}
    />
  );
}

export {
  Empty,
  EmptyHeader,
  EmptyTitle,
  EmptyDescription,
  EmptyContent,
  EmptyMedia,
};