Thẻ
Một khung có viền dành cho bảng điều khiển, các gói giá và bảng danh sách sản phẩm — bao gồm phần đầu trang, nội dung và phần chân trang, được áp dụng hiệu ứng bóng cứng theo phong cách tân thô mộc.
import { Button } from "@/components/ui/button"
import {
Card,Card nhóm các nội dung liên quan vào một khối độc lập, với các slot header, content và footer tùy chọn. Không có primitive nào bên dưới — đây chỉ là markup thuần được tạo kiểu theo công thức neobrutalist: một đường viền dày, một bóng đổ lệch đậm và tiêu đề dùng phông tiêu đề.
Nên dùng khi:
- Panel dashboard — ô số liệu, biểu đồ và dòng hoạt động xếp thành lưới.
- Các hạng giá — tên gói và giá ở header, tính năng ở phần thân, nút mua ở footer.
- Form độc lập — các khối đăng nhập, đăng ký và cài đặt cần một đường bao thị giác.
Cài đặt#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/card.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/card.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/card.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/card.json
Sao chép và dán đoạn mã sau vào dự án của bạn.
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded border-2 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-md [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"cn-font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t-2 bg-muted/50 p-(--card-spacing)",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
Cập nhật các đường dẫn import cho khớp với cấu trúc dự án của bạn.
Cách dùng#
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
<CardAction>Card Action</CardAction>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<p>Card Footer</p>
</CardFooter>
</Card>Kết hợp#
Dùng cách kết hợp sau để dựng một Card:
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooterVí dụ#
Kích thước#
Dùng prop size="sm" để đặt kích thước card thành nhỏ. Biến thể kích thước nhỏ dùng khoảng cách hẹp hơn.
import { ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Khoảng cách#
Ngoài prop size, bạn có thể dùng biến CSS --card-spacing để kiểm soát khoảng cách giữa các phần và phần đệm trong của các bộ phận card.
"use client"
import * as React from "react"Dùng lề âm với -mx-(--card-spacing) để nội dung trải từ cạnh này sang cạnh kia mà vẫn thẳng hàng với phần đệm trong của card. Khi nội dung trải cạnh-đến-cạnh nằm phía trên một phần chân, hãy dùng -mb-(--card-spacing) trên CardContent để bỏ khoảng trống giữa các phần.
import { Button } from "@/components/ui/button"
import {
Card,Hình ảnh#
Thêm một hình ảnh trước phần đầu card để tạo một card có hình ảnh.
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {RTL#
Để bật hỗ trợ RTL trong Neobrutalism, xem hướng dẫn cấu hình RTL.
"use client"
import * as React from "react"Khả năng truy cập#
Card hoàn toàn mang tính trình bày — mọi phần đều render một <div> thuần không có role ngầm định, nên nó không thêm gì vào cây khả năng truy cập. CardTitle không phải một phần tử tiêu đề: hãy lồng một <h2>–<h4> thật vào trong nó khi tiêu đề cần xuất hiện trong cấu trúc phân cấp của tài liệu. Với card có thể nhấp, hãy đặt liên kết hoặc nút vào bên trong card rồi trải vùng bấm của nó ra khắp bề mặt — đừng bao giờ gắn onClick lên chính <div> của card.
Tham chiếu API#
Card#
Thành phần Card là khối chứa gốc cho nội dung card.
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
className | string | - |
CardHeader#
Thành phần CardHeader được dùng cho tiêu đề, mô tả và một hành động tùy chọn.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardTitle#
Thành phần CardTitle được dùng cho tiêu đề của card.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardDescription#
Thành phần CardDescription được dùng cho văn bản trợ giúp bên dưới tiêu đề.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardAction#
Thành phần CardAction đặt nội dung ở góc trên bên phải của phần đầu (ví dụ, một nút hoặc một badge).
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardContent#
Thành phần CardContent được dùng cho phần thân chính của card.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardFooter#
Thành phần CardFooter được dùng cho các hành động và nội dung phụ ở phía dưới card.
| Prop | Type | Default |
|---|---|---|
className | string | - |
Nhật ký thay đổi#
Biến khoảng cách#
Nếu bạn đang nâng cấp từ một phiên bản trước của thành phần Card, bạn sẽ cần áp dụng các cập nhật sau để dùng biến --card-spacing:
Cập nhật các lớp khoảng cách của gốc Card.
Thay gap và padding dọc được viết cứng bằng --card-spacing, và đặt các giá trị kích thước mặc định và nhỏ ở gốc:
className={cn(
- "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
+ "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}Cập nhật các lớp khoảng cách của CardHeader.
Thay padding ngang và khoảng cách viền bằng biến dùng chung:
className={cn(
- "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}Cập nhật các lớp khoảng cách của CardContent và CardFooter.
Dùng --card-spacing cho phần đệm trong của nội dung và padding của phần chân:
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
- className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
+ className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
} className={cn(
- "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
+ "flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
className
)}Sau khi áp dụng các thay đổi này, bạn có thể tùy chỉnh khoảng cách của card bằng cách đặt --card-spacing trên Card với một lớp thuộc tính tùy ý:
function Example() {
return <Card className="[--card-spacing:--spacing(6)]">...</Card>
}