Bỏ qua đến nội dung

Trống

Một biểu tượng đại diện cho các kết quả tìm kiếm trống, bảng điều khiển trống và màn hình khởi động lần đầu — bao gồm biểu tượng, tiêu đề và hành động được bao quanh bởi các đường viền theo phong cách tân thô mộc.

import { IconFolderCode } from "@tabler/icons-react"
import { ArrowUpRightIcon } from "lucide-react"

Trạng thái rỗng lấp vào chỗ trống khi chẳng có gì để hiển thị — một chỗ dành cho biểu tượng hoặc ảnh đại diện, một tiêu đề, một dòng mô tả và một lời kêu gọi hành động. Đây là một bộ các thẻ div ghép được với nhau, không phụ thuộc thư viện nào (bên dưới không có primitive), tạo kiểu theo công thức neobrutalist: viền dày và kiểu chữ tiêu đề đậm.

Nên dùng khi:

  • Tìm kiếm và bộ lọc không ra kết quả — nói rõ vì sao danh sách trống và mở đường để đặt lại hoặc nới rộng truy vấn.
  • Màn hình dùng lần đầu — “Chưa có dự án nào” kèm một nút tạo mới chỉ thẳng vào bước tiếp theo.
  • Hộp thư và hàng đợi đã xử lý xong — cho người dùng biết danh sách trống là dấu hiệu thành công, không phải lỗi.

Cài đặt

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/empty.json
npx shadcn@latest add https://neobrutalism.com/r/base/empty.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/empty.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/empty.json

Sao chép và dán đoạn mã sau vào dự án của bạn.

components/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 w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded border-2 bg-card p-6 text-center text-balance",
        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: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
      },
    },
    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(
        "cn-font-heading text-sm font-medium tracking-tight",
        className
      )}
      {...props}
    />
  )
}

function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <div
      data-slot="empty-description"
      className={cn(
        "text-sm/relaxed 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-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance",
        className
      )}
      {...props}
    />
  )
}

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

Cập nhật các đường dẫn import cho phù hợp với thiết lập dự án của bạn.

Cách dùng

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"
<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <Icon />
    </EmptyMedia>
    <EmptyTitle>No data</EmptyTitle>
    <EmptyDescription>No data found</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Add data</Button>
  </EmptyContent>
</Empty>

Kết hợp

Dùng cách kết hợp sau để xây dựng một trạng thái Empty:

Empty
├── EmptyHeader
│   ├── EmptyMedia
│   ├── EmptyTitle
│   └── EmptyDescription
└── EmptyContent

Ví dụ

Viền

Dùng lớp tiện ích border để tạo một trạng thái trống có viền.

import { IconCloud } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

Nền

Dùng các tiện ích bg-*bg-gradient-* để thêm nền cho trạng thái trống.

import { IconBell } from "@tabler/icons-react"
import { RefreshCcwIcon } from "lucide-react"

Avatar

Dùng thành phần EmptyMedia để hiển thị một avatar trong trạng thái trống.

import {
  Avatar,
  AvatarFallback,

Nhóm avatar

Dùng thành phần EmptyMedia để hiển thị một nhóm avatar trong trạng thái trống.

import { PlusIcon } from "lucide-react"

import {

InputGroup

Bạn có thể thêm một thành phần InputGroup vào thành phần EmptyContent.

import { SearchIcon } from "lucide-react"

import {

RTL

Để bật hỗ trợ RTL trong Neobrutalism, hãy xem hướng dẫn cấu hình RTL.

"use client"

import * as React from "react"

Khả năng truy cập

Trạng thái rỗng chỉ mang tính trình bày — chỉ là các thẻ div thuần, không có role hay hành vi bàn phím nào, nên chẳng có gì phải kết nối thêm. Hãy đánh dấu biểu tượng EmptyMedia thuần trang trí bằng aria-hidden="true", và giữ các hành động trong EmptyContent ở dạng Button hoặc liên kết thật để chúng vẫn nhận được focus. Khi trạng thái rỗng thay thế nội dung sau một tác vụ bất đồng bộ (một lượt tìm kiếm trả về không kết quả), hãy thông báo qua live region — bản thân việc thay đổi DOM là hoàn toàn im lặng đối với trình đọc màn hình.

Tham chiếu API

Empty

Thành phần chính của trạng thái trống. Bao bọc các thành phần EmptyHeaderEmptyContent.

PropTypeDefault
classNamestring
<Empty>
  <EmptyHeader />
  <EmptyContent />
</Empty>

EmptyHeader

Thành phần EmptyHeader bao bọc phần media, tiêu đề và mô tả của trạng thái trống.

PropTypeDefault
classNamestring
<EmptyHeader>
  <EmptyMedia />
  <EmptyTitle />
  <EmptyDescription />
</EmptyHeader>

EmptyMedia

Dùng thành phần EmptyMedia để hiển thị phần media của trạng thái trống, chẳng hạn như biểu tượng hoặc hình ảnh. Bạn cũng có thể dùng nó để hiển thị các thành phần khác như avatar.

PropTypeDefault
variant"default" | "icon"default
classNamestring
<EmptyMedia variant="icon">
  <Icon />
</EmptyMedia>
<EmptyMedia>
  <Avatar>
    <AvatarImage src="..." />
    <AvatarFallback>CN</AvatarFallback>
  </Avatar>
</EmptyMedia>

EmptyTitle

Dùng thành phần EmptyTitle để hiển thị tiêu đề của trạng thái trống.

PropTypeDefault
classNamestring
<EmptyTitle>No data</EmptyTitle>

EmptyDescription

Dùng thành phần EmptyDescription để hiển thị mô tả của trạng thái trống.

PropTypeDefault
classNamestring
<EmptyDescription>You do not have any notifications.</EmptyDescription>

EmptyContent

Dùng thành phần EmptyContent để hiển thị nội dung của trạng thái trống, chẳng hạn như một nút, ô nhập hoặc một liên kết.

PropTypeDefault
classNamestring
<EmptyContent>
  <Button>Add Project</Button>
</EmptyContent>