Glrk UI

Empty

A simplified wrapper for Shadcn Empty with flexible option handling and automatic type conversion

No Messages
You haven't received any messages yet. When someone contacts you, they'll show up here.

Installation

npx shadcn@latest add @glrk-ui/empty

If you haven't set up the prerequisites yet, check out Prerequest section.

Copy and paste the following code into shadcn empty component.

ui/empty.tsx
type EmptyWrapperProps = {
  title?: React.ReactNode
  description?: React.ReactNode
  media?: React.ReactNode
  content?: React.ReactNode
  wrapperCls?: string
  headerCls?: string
  titleCls?: string
  mediaCls?: string
  descriptionCls?: string
  contentCls?: string
  mediaVariant?: VariantProps<typeof emptyMediaVariants>["variant"]
}

function EmptyWrapper({
  title,
  description,
  media,
  content,
  wrapperCls,
  headerCls,
  titleCls,
  mediaCls,
  descriptionCls,
  contentCls,
  mediaVariant = "default",
}: EmptyWrapperProps) {
  return (
    <Empty className={wrapperCls}>
      <EmptyHeader className={headerCls}>
        {media && <EmptyMedia className={mediaCls} variant={mediaVariant}>{media}</EmptyMedia>}
        {title && <EmptyTitle className={titleCls}>{title}</EmptyTitle>}
        {description && <EmptyDescription className={descriptionCls}>{description}</EmptyDescription>}
      </EmptyHeader>

      {
        content &&
        <EmptyContent className={contentCls}>
          {content}
        </EmptyContent>
      }
    </Empty>
  )
}
export {
  Empty,
  EmptyHeader,
  EmptyTitle,
  EmptyDescription,
  EmptyContent,
  EmptyMedia,
  EmptyWrapper,
}

empty.tsx

Copy and paste the following code into your project.

ui/empty.tsx
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-dashed 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 text-center",
        className
      )}
      {...props}
    />
  )
}

const emptyMediaVariants = cva(
  "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "bg-transparent",
        icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

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

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

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

type EmptyWrapperProps = {
  title?: React.ReactNode
  description?: React.ReactNode
  media?: React.ReactNode
  content?: React.ReactNode
  wrapperCls?: string
  headerCls?: string
  titleCls?: string
  mediaCls?: string
  descriptionCls?: string
  contentCls?: string
  mediaVariant?: VariantProps<typeof emptyMediaVariants>["variant"]
}

function EmptyWrapper({
  title,
  description,
  media,
  content,
  wrapperCls,
  headerCls,
  titleCls,
  mediaCls,
  descriptionCls,
  contentCls,
  mediaVariant = "default",
}: EmptyWrapperProps) {
  return (
    <Empty className={wrapperCls}>
      <EmptyHeader className={headerCls}>
        {media && <EmptyMedia className={mediaCls} variant={mediaVariant}>{media}</EmptyMedia>}
        {title && <EmptyTitle className={titleCls}>{title}</EmptyTitle>}
        {description && <EmptyDescription className={descriptionCls}>{description}</EmptyDescription>}
      </EmptyHeader>

      {
        content &&
        <EmptyContent className={contentCls}>
          {content}
        </EmptyContent>
      }
    </Empty>
  )
}

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

Done

You can now use EmptyWrapper

Usage

Basic

import { Inbox } from "lucide-react"
import { EmptyWrapper } from "@/components/ui/empty"
import { Button } from "@/components/ui/button"

export function Basic() {
  return (
    <EmptyWrapper
      title="No Messages"
      description="You haven't received any messages yet. When someone contacts you, they'll show up here."
      media={<Inbox className="size-10 text-muted-foreground" />}
      content={<Button>Compose Message</Button>}
    />
  )
}

Custom Styling

import { EmptyWrapper } from "@/components/ui/empty";
import { AlertCircle } from "lucide-react";

export function CustomStyling() {
  return (
    <EmptyWrapper
      media={<AlertCircle />}
      mediaVariant="icon"
      title="No results found"
      description="Try adjusting your search or filter to find what you're looking for"
      wrapperCls="border-2 bg-muted/50"
      titleCls="text-destructive"
      mediaCls="bg-destructive/10"
    />
  )
}

Reference

EmptyWrapper

Prop

Type