import { Button } from "@/components/ui/button"
import {
Card,카드는 관련된 콘텐츠를 헤더, 콘텐츠, 푸터 슬롯을 선택적으로 갖춘 독립적인 박스로 묶습니다. 아래에 깔린 프리미티브는 없습니다. 평범한 마크업에 네오브루탈리즘 공식, 즉 두꺼운 테두리와 선명하게 오프셋된 그림자, 제목 서체 타이틀을 입힌 것입니다.
다음과 같은 경우에 사용합니다.
- 대시보드 패널 — 지표 타일, 차트, 활동 피드를 그리드에 배치합니다.
- 요금제 등급 — 헤더에 플랜 이름과 가격, 본문에 기능 목록, 푸터에 구매 버튼을 둡니다.
- 독립형 폼 — 시각적 경계가 필요한 로그인, 가입, 설정 박스.
설치#
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
다음 코드를 복사해 프로젝트에 붙여넣습니다.
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,
}
프로젝트 설정에 맞게 import 경로를 업데이트합니다.
사용법#
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>구성#
Card를 만들려면 다음 구성을 사용합니다:
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooter예제#
크기#
size="sm" prop을 사용해 카드의 크기를 작게 설정합니다. 작은 크기 변형은 더 좁은 간격을 사용합니다.
import { ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"간격#
size prop 외에도, --card-spacing CSS 변수를 사용해 섹션 사이의 간격과 카드 각 부분의 인셋을 제어할 수 있습니다.
"use client"
import * as React from "react"-mx-(--card-spacing)로 음수 마진을 사용하면, 카드 인셋에 맞춰 정렬을 유지하면서 콘텐츠를 끝에서 끝까지 넓힐 수 있습니다. 끝에서 끝까지 넓어지는 콘텐츠가 푸터 위에 놓일 때는, CardContent에 -mb-(--card-spacing)를 적용해 섹션 간격을 제거합니다.
import { Button } from "@/components/ui/button"
import {
Card,이미지#
카드 헤더 앞에 이미지를 추가하여 이미지가 있는 카드를 만듭니다.
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {RTL#
Neobrutalism에서 RTL을 활성화하는 방법은 RTL 설정 가이드를 참조하세요.
"use client"
import * as React from "react"접근성#
카드는 순전히 표현용입니다. 모든 부위가 암묵적 role이 없는 평범한 <div>로 렌더링되므로 접근성 트리에는 아무것도 추가하지 않습니다. CardTitle은 제목 요소가 아닙니다. 타이틀이 문서 개요에 나타나야 한다면 그 안에 실제 <h2>~<h4>를 중첩하세요. 클릭 가능한 카드를 만들 때는 링크나 버튼을 카드 안에 두고 클릭 영역을 카드 표면 전체로 늘리세요. 카드 <div> 자체에 onClick을 붙이면 안 됩니다.
API 참조#
Card#
Card 컴포넌트는 카드 콘텐츠의 루트 컨테이너입니다.
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
className | string | - |
CardHeader#
CardHeader 컴포넌트는 제목, 설명, 그리고 선택적 액션에 사용합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardTitle#
CardTitle 컴포넌트는 카드 제목에 사용합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardDescription#
CardDescription 컴포넌트는 제목 아래에 표시하는 보조 텍스트에 사용합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardAction#
CardAction 컴포넌트는 헤더의 오른쪽 위에 콘텐츠(예: 버튼이나 배지)를 배치합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardContent#
CardContent 컴포넌트는 카드의 본문에 사용합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
CardFooter#
CardFooter 컴포넌트는 카드 하단의 액션이나 보조 콘텐츠에 사용합니다.
| Prop | Type | Default |
|---|---|---|
className | string | - |
변경 로그#
간격 변수#
이전 버전의 Card 컴포넌트에서 업그레이드하는 경우, --card-spacing 변수를 사용하려면 다음 업데이트를 적용해야 합니다:
Card 루트의 간격 클래스를 업데이트합니다.
하드코딩된 gap과 세로 방향 패딩을 --card-spacing으로 교체하고, 루트에 기본값과 작은 크기 값을 설정합니다:
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
)}CardHeader의 간격 클래스를 업데이트합니다.
가로 방향 패딩과 테두리 간격을 공유 변수로 교체합니다:
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
)}CardContent와 CardFooter의 간격 클래스를 업데이트합니다.
콘텐츠 인셋과 푸터 패딩에는 --card-spacing을 사용합니다:
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
)}이러한 변경을 적용하면, 임의 속성 클래스로 Card에 --card-spacing을 설정하여 카드 간격을 커스터마이즈할 수 있습니다:
function Example() {
return <Card className="[--card-spacing:--spacing(6)]">...</Card>
}