Card

Interactive UI Explorer

아래에서 각 컴포넌트의 다양한 Variants States를 문서화된 형태로 테스트해볼 수 있습니다.

Live Preview

디자인 시스템 v2

일관된 토큰과 접근성을 갖춘 컴포넌트 모음.
RY3일 전 업데이트

Card

콘텐츠를 묶는 카드 컨테이너입니다. variant·padding과 CardHeader/Title/Content/Footer 조합을 지원합니다.

Variants

soft / elevated / outlined — 모던 톤은 soft·elevated를 권장합니다.

Soft
<Card variant="soft">…</Card>

Soft

slate ring + 옅은 그림자

Live Preview
Elevated
<Card variant="elevated">…</Card>

Elevated

떠 있는 느낌의 그림자

Live Preview
Outlined
<Card variant="outlined">…</Card>

Outlined

테두리만 강조

Live Preview

구성 (Header / Content / Footer)

하위 컴포넌트를 조합해 머리말·본문·꼬리말 구조를 만듭니다.

Source Code
<Card variant="soft">
  <CardHeader>
    <CardTitle>제목</CardTitle>
    <CardContent>설명</CardContent>
  </CardHeader>
  <CardFooter>액션</CardFooter>
</Card>

디자인 시스템 v2

일관된 토큰과 접근성을 갖춘 컴포넌트 모음입니다.
3일 전 업데이트
Live Preview
Implementation

제작 코드

이 컴포넌트가 실제로 어떻게 구현되어 있는지 — 본체 .tsx 파일을 그대로 보여줍니다. variant 매핑·접근성 처리·forwardRef 패턴 등 디테일을 그대로 확인할 수 있어요.

Cardtypescript
/**
 * Card 컴포넌트
 * 
 * 다양한 스타일의 카드 컴포넌트입니다.
 */
import { HTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/cn';

export interface CardProps extends HTMLAttributes<HTMLDivElement> {
  variant?: 'default' | 'elevated' | 'outlined' | 'filled' | 'soft' | 'soft-muted';
  padding?: 'none' | 'sm' | 'md' | 'lg';
}

const variantStyles: Record<NonNullable<CardProps['variant']>, string> = {
  default: 'bg-white border border-black',
  elevated: 'bg-white border border-black shadow-lg',
  outlined: 'bg-transparent border-2 border-black',
  filled: 'bg-black border border-black',
  // 라이트 톤 — 부드러운 slate ring + 약한 그림자. 인사이트·콘텐츠 페이지의 정보형 카드.
  soft: 'rounded-2xl bg-white border border-slate-200 shadow-sm',
  // soft의 배경 muted 변형 — WIP·placeholder·보조 정보 카드.
  'soft-muted': 'rounded-2xl bg-slate-50/60 border border-slate-200',
};

const paddingStyles: Record<NonNullable<CardProps['padding']>, string> = {
  none: '',
  sm: 'p-4',
  md: 'p-6',
  lg: 'p-8',
};

export const Card = forwardRef<HTMLDivElement, CardProps>(
  ({ className, variant = 'default', padding = 'md', children, ...props }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          'rounded-lg',
          variantStyles[variant],
          paddingStyles[padding],
          className
        )}
        {...props}
      >
        {children}
      </div>
    );
  }
);

Card.displayName = 'Card';

// Card 하위 컴포넌트
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('mb-4', className)}
      {...props}
    />
  )
);
CardHeader.displayName = 'CardHeader';

export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
  ({ className, ...props }, ref) => (
    <h3
      ref={ref}
      className={cn('text-heading3 font-semibold', className)}
      {...props}
    />
  )
);
CardTitle.displayName = 'CardTitle';

export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('text-body2 text-zinc-600 dark:text-zinc-400', className)}
      {...props}
    />
  )
);
CardContent.displayName = 'CardContent';

export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('mt-4 pt-4 border-t border-zinc-200 dark:border-zinc-700', className)}
      {...props}
    />
  )
);
CardFooter.displayName = 'CardFooter';