Skip to content

Empty

Install this component.

Installation

npx shadcn@latest add https://ui.monark.io/r/empty.json

Source

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"

const emptyVariants = cva(
  "group/empty flex w-full min-w-0 flex-col gap-3 rounded-2xl border border-dashed text-sm text-muted-foreground",
  {
    variants: {
      align: {
        center: "items-center text-center",
        start: "items-start text-left",
      },
      size: {
        sm: "p-5",
        default: "px-4 py-8 sm:p-8",
        lg: "gap-4 rounded-3xl p-6 sm:p-10",
      },
    },
    defaultVariants: { align: "center", size: "default" },
  }
)

/**
 * Dashed placeholder for a list, table or panel with nothing in it yet.
 * Compose EmptyMedia, EmptyTitle, EmptyDescription and EmptyContent (actions)
 * inside. Pass `role="status"` when it replaces results the user just
 * filtered away, so the change is announced.
 */
function Empty({
  className,
  align = "center",
  size = "default",
  ...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyVariants>) {
  return (
    <div
      data-slot="empty"
      data-align={align}
      data-size={size}
      className={cn(emptyVariants({ align, size }), className)}
      {...props}
    />
  )
}

function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-header"
      className={cn(
        "flex max-w-[48ch] flex-col gap-1 group-data-[align=center]/empty:items-center",
        className
      )}
      {...props}
    />
  )
}

/** Icon or illustration above the title. Decorative: rendered aria-hidden. */
function EmptyMedia({
  className,
  variant = "icon",
  ...props
}: React.ComponentProps<"div"> & { variant?: "icon" | "plain" }) {
  return (
    <div
      data-slot="empty-media"
      data-variant={variant}
      aria-hidden="true"
      className={cn(
        "flex shrink-0 items-center justify-center text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
        variant === "icon" &&
          "size-11 rounded-full bg-muted text-foreground [&_svg:not([class*='size-'])]:size-5",
        className
      )}
      {...props}
    />
  )
}

function EmptyTitle({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="empty-title"
      className={cn(
        "text-base font-bold text-foreground group-data-[size=lg]/empty:text-lg",
        className
      )}
      {...props}
    />
  )
}

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

/** Action slot: buttons or links, wrapped in a row. */
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-content"
      className={cn(
        "mt-1 flex flex-wrap items-center gap-2 group-data-[align=center]/empty:justify-center",
        className
      )}
      {...props}
    />
  )
}

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