컴포넌트 스펙
모든 컴포넌트는 cn() 유틸리티(src/utils/cn.ts)를 사용하고, CSS 변수 토큰을 통해 색상을 지정한다.
Button
변형 (Variant)
| Variant |
배경 |
텍스트 |
테두리 |
용도 |
primary |
--color-primary |
white |
none |
주요 CTA |
secondary |
--color-secondary |
--color-secondary-text |
none |
보조 액션 |
accent |
--color-accent |
--color-accent-text |
none |
포인트 액션 |
outline |
transparent |
--color-primary |
--color-primary |
조용한 강조 |
ghost |
transparent |
--color-text-secondary |
none |
최소 강조 |
danger |
--color-danger |
white |
none |
삭제, 경고 |
사이즈
| Size |
padding |
font-size |
height |
border-radius |
sm |
px-3 py-1.5 |
text-sm |
32px |
rounded-md |
md |
px-4 py-2 |
text-base |
40px |
rounded-lg |
lg |
px-6 py-3 |
text-lg |
48px |
rounded-lg |
상태
- disabled:
opacity-50 cursor-not-allowed pointer-events-none
- loading: 스피너 아이콘 표시, 텍스트 유지, disabled 처리
- hover: 각 variant에 맞는
-hover 토큰 적용
- active: 각 variant에 맞는
-active 토큰 적용
- focus-visible:
ring-2 ring-[var(--color-primary)] ring-offset-2
구현 예시
const buttonVariants = {
primary: 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-active)]',
secondary: 'bg-[var(--color-secondary)] text-[var(--color-secondary-text)] hover:bg-[var(--color-secondary-hover)]',
accent: 'bg-[var(--color-accent)] text-[var(--color-accent-text)] hover:bg-[var(--color-accent-hover)]',
outline: 'border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary-subtle)]',
ghost: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] hover:text-[var(--color-text-primary)]',
danger: 'bg-[var(--color-danger)] text-white hover:opacity-90',
};
Badge
상태, 카테고리, 태그 표시에 사용한다.
변형
| Variant |
배경 |
텍스트 |
default |
--color-bg-elevated |
--color-text-primary |
primary |
--color-primary-subtle |
--color-primary-text |
success |
#DCFCE7 |
#166534 |
warning |
#FEF9C3 |
#854D0E |
danger |
#FEE2E2 |
#991B1B |
info |
#E0F2FE |
#075985 |
스펙
- 패딩:
px-2 py-0.5
- 폰트:
text-xs font-medium
- 모양:
rounded-full (pill) 또는 rounded-md (square)
Card
콘텐츠를 담는 기본 컨테이너.
구조
<div class="rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-surface)] shadow-sm">
<!-- Card Header -->
<div class="border-b border-[var(--color-border)] px-6 py-4">
<h3 class="text-base font-semibold text-[var(--color-text-primary)]">제목</h3>
<p class="text-sm text-[var(--color-text-secondary)]">부제목</p>
</div>
<!-- Card Body -->
<div class="p-6">
<!-- 콘텐츠 -->
</div>
<!-- Card Footer (선택) -->
<div class="border-t border-[var(--color-border)] px-6 py-4">
<!-- 액션 버튼 등 -->
</div>
</div>
변형
| Variant |
설명 |
default |
border + shadow-sm |
elevated |
shadow-md, 모달 내부 카드 |
flat |
border만, shadow 없음 |
interactive |
hover 시 shadow-md + scale-[1.01] |
Input
기본 스펙
<div class="flex flex-col gap-1.5">
<label class="text-sm font-medium text-[var(--color-text-primary)]">레이블</label>
<input
class="
w-full rounded-lg border border-[var(--color-border)]
bg-[var(--color-bg-surface)] px-3 py-2
text-base text-[var(--color-text-primary)]
placeholder:text-[var(--color-text-tertiary)]
focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent
disabled:opacity-50 disabled:cursor-not-allowed
"
placeholder="입력하세요"
/>
<p class="text-xs text-[var(--color-text-tertiary)]">도움말 텍스트</p>
</div>
상태
| 상태 |
테두리 색상 |
| 기본 |
--color-border |
| 포커스 |
--color-primary (ring) |
| 오류 |
--color-danger |
| 성공 |
--color-success |
| 비활성 |
--color-border + opacity-50 |
사이즈
| Size |
padding |
font-size |
height |
sm |
px-3 py-1.5 |
text-sm |
32px |
md |
px-3 py-2 |
text-base |
40px |
lg |
px-4 py-3 |
text-lg |
48px |
Modal
구조
<!-- Backdrop -->
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<!-- Dialog -->
<div class="
relative w-full max-w-md mx-4 rounded-2xl
bg-[var(--color-bg-elevated)] shadow-xl
">
<!-- Header -->
<div class="flex items-center justify-between border-b border-[var(--color-border)] px-6 py-4">
<h2 class="text-lg font-semibold text-[var(--color-text-primary)]">제목</h2>
<button class="...">✕</button>
</div>
<!-- Body -->
<div class="px-6 py-6">...</div>
<!-- Footer -->
<div class="flex justify-end gap-3 border-t border-[var(--color-border)] px-6 py-4">
<Button variant="ghost">취소</Button>
<Button variant="primary">확인</Button>
</div>
</div>
</div>
사이즈
| Size |
max-width |
sm |
max-w-sm (384px) |
md |
max-w-md (448px) |
lg |
max-w-lg (512px) |
xl |
max-w-xl (576px) |
접근성
role="dialog", aria-modal="true", aria-labelledby 필수
- 열릴 때 첫 번째 포커스 가능 요소로 포커스 이동
- Escape 키로 닫기
- Backdrop 클릭 시 닫기 (선택적)
- 열린 동안 body 스크롤 잠금
Toast
위치
fixed bottom-4 right-4 z-[60] (Modal보다 위)
변형
| Variant |
아이콘 |
색상 |
success |
CheckCircle |
--color-success |
warning |
AlertTriangle |
--color-warning |
error |
XCircle |
--color-danger |
info |
Info |
--color-info |
구조
<div class="
flex items-start gap-3 w-80 rounded-xl p-4
bg-[var(--color-bg-elevated)] shadow-lg
border border-[var(--color-border)]
">
<!-- 아이콘 -->
<!-- 텍스트 -->
<!-- 닫기 버튼 -->
</div>
동작
- 기본 자동 닫힘: 4000ms
- 진입:
translate-y-2 opacity-0 → translate-y-0 opacity-100
- 퇴장:
translate-y-0 opacity-100 → translate-y-2 opacity-0
- 트랜지션:
duration-200 ease-out