Checkbox
개요
Checkbox는 사용자가 여러 옵션 중 하나 이상을 선택할 수 있는 폼 컴포넌트입니다. Cals 예약 관리 시스템에서는 부가 서비스 선택, 약관 동의, 예약 옵션 선택 등에서 활용됩니다.
Cals 특화 기능:
- 예약 상태별 색상 시스템으로 선택 상태 시각화
- 부가 서비스 및 옵션 다중 선택 최적화
- 약관 동의 및 알림 설정을 위한 체크박스
- 예약 필터링을 위한 다중 상태 선택
설치
npx @vortex/cli add checkbox --package cals기본 사용법
import "@vortex/ui-cals/theme";
import { Checkbox } from "@vortex/ui-cals";
export default function ServiceOptions() {
return (
<div className="flex items-center space-x-2">
<Checkbox id="shampoo" />
<label htmlFor="shampoo" className="text-sm font-medium">
샴푸 추가 (+₩10,000)
</label>
</div>
);
}Variants
Default
기본 스타일의 Checkbox입니다.
<div className="flex items-center space-x-2">
<Checkbox id="option1" />
<label htmlFor="option1">기본 옵션</label>
</div>With Label
레이블과 함께 사용하는 Checkbox입니다.
<div className="flex items-center space-x-2">
<Checkbox id="option2" />
<label htmlFor="option2" className="text-sm font-medium">
레이블이 있는 옵션
</label>
</div>With Description
설명과 함께 사용하는 Checkbox입니다.
<div className="flex items-start space-x-2">
<Checkbox id="option3" className="mt-1" />
<div>
<label htmlFor="option3" className="text-sm font-medium">
옵션 제목
</label>
<p className="text-sm text-gray-500">옵션에 대한 상세 설명</p>
</div>
</div>Sizes
Small
작은 크기의 Checkbox입니다.
<Checkbox className="h-4 w-4" />Medium (Default)
기본 크기의 Checkbox입니다.
<Checkbox className="h-5 w-5" />Large
큰 크기의 Checkbox입니다.
<Checkbox className="h-6 w-6" />States
Default
기본 상태의 Checkbox입니다.
<Checkbox />Checked
선택된 Checkbox입니다.
<Checkbox checked />Disabled
비활성화된 Checkbox입니다.
<Checkbox disabled />Disabled Checked
비활성화되고 선택된 Checkbox입니다.
<Checkbox checked disabled />Indeterminate
부분 선택 상태의 Checkbox입니다. (그룹 선택 시)
<Checkbox checked="indeterminate" />🆕 Cals 브랜딩
브랜드 컬러
Cals Checkbox는 기본적으로 Cals의 Primary 색상(Pink #e91e63)을 체크 상태에서 사용합니다.
<div className="space-y-4">
{/* Primary (Pink) - 기본 체크 */}
<div className="flex items-center space-x-2">
<Checkbox
id="primary"
checked
className="data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor="primary">Primary (Pink) - 기본 서비스</label>
</div>
{/* Secondary (Blue) - 정보성 옵션 */}
<div className="flex items-center space-x-2">
<Checkbox
id="secondary"
checked
className="data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<label htmlFor="secondary">Secondary (Blue) - 알림 설정</label>
</div>
{/* Accent (Purple) - 강조 옵션 */}
<div className="flex items-center space-x-2">
<Checkbox
id="accent"
checked
className="data-[state=checked]:bg-[#9c27b0] data-[state=checked]:border-[#9c27b0]"
/>
<label htmlFor="accent">Accent (Purple) - 프리미엄 옵션</label>
</div>
</div>예약 상태 컬러
Cals Checkbox는 예약 관리의 5가지 상태를 반영하여 선택 옵션의 시각적 피드백을 제공합니다.
<div className="space-y-3">
{/* Available - 선택 가능 */}
<div className="flex items-center space-x-2">
<Checkbox
id="available"
className="data-[state=checked]:bg-[#4caf50] data-[state=checked]:border-[#4caf50]"
/>
<label htmlFor="available" className="text-sm">
<span className="font-medium">예약 가능</span>
<span className="text-gray-500"> - 즉시 선택 가능한 옵션</span>
</label>
</div>
{/* Pending - 확인 필요 */}
<div className="flex items-center space-x-2">
<Checkbox
id="pending"
checked
disabled
className="data-[state=checked]:bg-[#ff9800] data-[state=checked]:border-[#ff9800]"
/>
<label htmlFor="pending" className="text-sm text-gray-500">
<span className="font-medium">승인 대기</span>
<span> - 담당자 확인 필요</span>
</label>
</div>
{/* Confirmed - 확정된 선택 */}
<div className="flex items-center space-x-2">
<Checkbox
id="confirmed"
checked
disabled
className="data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<label htmlFor="confirmed" className="text-sm text-gray-500">
<span className="font-medium">예약 확정</span>
<span> - 확정된 부가 서비스</span>
</label>
</div>
{/* Cancelled - 취소된 옵션 */}
<div className="flex items-center space-x-2">
<Checkbox
id="cancelled"
disabled
className="data-[state=checked]:bg-[#f44336] data-[state=checked]:border-[#f44336]"
/>
<label htmlFor="cancelled" className="text-sm text-gray-500 line-through">
<span className="font-medium">예약 취소</span>
<span> - 취소된 옵션</span>
</label>
</div>
{/* Completed - 완료된 서비스 */}
<div className="flex items-center space-x-2">
<Checkbox
id="completed"
checked
disabled
className="data-[state=checked]:bg-[#9c27b0] data-[state=checked]:border-[#9c27b0]"
/>
<label htmlFor="completed" className="text-sm text-gray-500">
<span className="font-medium">서비스 완료</span>
<span> - 완료된 부가 서비스</span>
</label>
</div>
</div>Cals 특화 사용 가이드
부가 서비스 선택
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-900">
부가 서비스 선택 (선택사항)
</h3>
<div className="space-y-2 pl-2">
<div className="flex items-center space-x-2">
<Checkbox
id="shampoo"
className="data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor="shampoo" className="text-sm">
<span className="font-medium">샴푸</span>
<span className="text-gray-500"> (+₩10,000, 20분)</span>
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="treatment"
className="data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor="treatment" className="text-sm">
<span className="font-medium">트리트먼트</span>
<span className="text-gray-500"> (+₩50,000, 40분)</span>
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="scalp"
className="data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor="scalp" className="text-sm">
<span className="font-medium">두피 케어</span>
<span className="text-gray-500"> (+₩30,000, 30분)</span>
</label>
</div>
</div>
</div>약관 동의
<div className="space-y-3 p-4 border border-gray-200 rounded-md">
<div className="flex items-start space-x-2">
<Checkbox
id="terms-all"
checked="indeterminate"
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor="terms-all" className="text-sm font-medium">
전체 동의
</label>
</div>
<div className="pl-6 space-y-2">
<div className="flex items-start space-x-2">
<Checkbox
id="terms-required"
checked
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<div>
<label htmlFor="terms-required" className="text-sm">
<span className="font-medium">[필수]</span> 서비스 이용약관
</label>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="terms-privacy"
checked
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<div>
<label htmlFor="terms-privacy" className="text-sm">
<span className="font-medium">[필수]</span> 개인정보 처리방침
</label>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="terms-marketing"
className="mt-1 data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<div>
<label htmlFor="terms-marketing" className="text-sm">
<span className="font-medium text-gray-600">[선택]</span> 마케팅 정보
수신 동의
</label>
<p className="text-xs text-gray-500">
이벤트 및 프로모션 안내를 받을 수 있습니다
</p>
</div>
</div>
</div>
</div>알림 설정
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-900">알림 설정</h3>
<div className="space-y-2 pl-2">
<div className="flex items-start space-x-2">
<Checkbox
id="notif-sms"
checked
className="mt-1 data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<div>
<label htmlFor="notif-sms" className="text-sm font-medium">
SMS 알림
</label>
<p className="text-xs text-gray-500">
예약 확정, 예약 변경, 당일 알림을 받습니다
</p>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="notif-email"
checked
className="mt-1 data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<div>
<label htmlFor="notif-email" className="text-sm font-medium">
이메일 알림
</label>
<p className="text-xs text-gray-500">
예약 내역 및 영수증을 이메일로 받습니다
</p>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="notif-push"
className="mt-1 data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<div>
<label htmlFor="notif-push" className="text-sm font-medium">
앱 푸시 알림
</label>
<p className="text-xs text-gray-500">
모바일 앱에서 실시간 알림을 받습니다
</p>
</div>
</div>
</div>
</div>🆕 Foundation/iCignal과의 차이점
| 속성 | Foundation | iCignal | Cals |
|---|---|---|---|
| Primary Color | #3B82F6 (Blue) | #2196f3 (Blue) | #e91e63 (Pink) |
| Secondary Color | #10B981 (Green) | #4caf50 (Green) | #03a9f4 (Blue) |
| Accent Color | #8B5CF6 (Purple) | #ff5722 (Deep Orange) | #9c27b0 (Purple) |
| 사용 맥락 | 범용 웹 애플리케이션 | Analytics 대시보드 | 예약 관리 시스템 |
| 예약 상태 | 없음 | 없음 | 5가지 상태 (Available/Pending/Confirmed/Cancelled/Completed) |
| 주요 사용 | 범용 다중 선택 | 데이터 필터 선택 | 부가 서비스, 약관 동의, 알림 설정 |
| 체크 스타일 | Blue background | Blue background | Pink background (Primary) |
| 그룹 선택 | 기본 그룹화 | 필터 그룹화 | 서비스 옵션, 약관 계층 구조 |
사용 시나리오 비교
Foundation Checkbox: 범용 다중 선택, 설정 토글, 항목 선택 iCignal Checkbox: 데이터 필터, 차트 시리즈 선택, 분석 옵션 Cals Checkbox: 부가 서비스 선택, 약관 동의, 알림 설정, 예약 옵션
Props API
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | 'indeterminate' | false | 체크 상태 |
defaultChecked | boolean | false | 기본 체크 상태 (비제어 컴포넌트) |
onCheckedChange | (checked: boolean) => void | - | 체크 상태 변경 이벤트 핸들러 |
disabled | boolean | false | 비활성화 여부 |
required | boolean | false | 필수 선택 여부 |
name | string | - | Form name 속성 |
value | string | 'on' | Form value 속성 |
id | string | - | HTML id 속성 (label 연결용) |
className | string | - | 추가 CSS 클래스 |
접근성
Checkbox 컴포넌트는 웹 접근성 표준을 준수합니다:
- Radix UI 기반: WAI-ARIA 1.2 Checkbox 패턴 구현
- 시맨틱 마크업:
role="checkbox"및 적절한 ARIA 속성 - 키보드 네비게이션:
Space: 체크박스 토글Tab: 다음 체크박스로 이동Shift + Tab: 이전 체크박스로 이동
- 스크린 리더:
aria-checked상태 안내 - Label 연결:
htmlFor와id를 통한 label-checkbox 연결 - Focus 표시: 명확한 focus ring으로 현재 위치 표시
- Required 표시:
aria-required="true"지원
접근성 예제
<div className="flex items-start space-x-2">
<Checkbox
id="terms"
required
aria-required="true"
aria-describedby="terms-description"
/>
<div>
<label htmlFor="terms" className="text-sm font-medium">
[필수] 서비스 이용약관 동의
</label>
<p id="terms-description" className="text-xs text-gray-500">
서비스 이용을 위해 반드시 동의해야 합니다
</p>
</div>
</div>예제
부가 서비스 선택 폼
import { Checkbox } from "@vortex/ui-cals";
import { useState } from "react";
export default function AdditionalServicesForm() {
const [selectedServices, setSelectedServices] = useState<string[]>([]);
const services = [
{ id: "shampoo", name: "샴푸", price: 10000, duration: 20 },
{ id: "treatment", name: "트리트먼트", price: 50000, duration: 40 },
{ id: "scalp", name: "두피 케어", price: 30000, duration: 30 },
{ id: "clinic", name: "클리닉", price: 80000, duration: 60 },
];
const totalPrice = services
.filter((s) => selectedServices.includes(s.id))
.reduce((sum, s) => sum + s.price, 0);
const totalDuration = services
.filter((s) => selectedServices.includes(s.id))
.reduce((sum, s) => sum + s.duration, 0);
return (
<div className="space-y-4 max-w-md">
<h3 className="text-base font-semibold text-gray-900">
부가 서비스 선택 (선택사항)
</h3>
<div className="space-y-3">
{services.map((service) => (
<div key={service.id} className="flex items-center space-x-2">
<Checkbox
id={service.id}
checked={selectedServices.includes(service.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedServices([...selectedServices, service.id]);
} else {
setSelectedServices(
selectedServices.filter((id) => id !== service.id)
);
}
}}
className="data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<label htmlFor={service.id} className="text-sm cursor-pointer">
<span className="font-medium">{service.name}</span>
<span className="text-gray-500">
{" "}
(+₩{service.price.toLocaleString()}, {service.duration}분)
</span>
</label>
</div>
))}
</div>
{selectedServices.length > 0 && (
<div className="p-3 bg-pink-50 rounded-md border border-[#e91e63]/20">
<div className="text-sm text-gray-700">
<p className="font-medium text-[#e91e63]">선택한 부가 서비스</p>
<p className="mt-1">
추가 비용:{" "}
<span className="font-semibold">
₩{totalPrice.toLocaleString()}
</span>
</p>
<p>
추가 시간:{" "}
<span className="font-semibold">{totalDuration}분</span>
</p>
</div>
</div>
)}
</div>
);
}약관 동의 폼
import { Checkbox } from "@vortex/ui-cals";
import { useState } from "react";
export default function TermsAgreementForm() {
const [agreements, setAgreements] = useState({
termsOfService: false,
privacyPolicy: false,
marketing: false,
});
const allRequired = agreements.termsOfService && agreements.privacyPolicy;
const allChecked = allRequired && agreements.marketing;
const handleAllCheck = (checked: boolean) => {
setAgreements({
termsOfService: checked,
privacyPolicy: checked,
marketing: checked,
});
};
return (
<div className="space-y-4 max-w-md p-4 border border-gray-200 rounded-lg">
<div className="flex items-start space-x-2 pb-3 border-b border-gray-200">
<Checkbox
id="terms-all"
checked={allChecked ? true : allRequired ? "indeterminate" : false}
onCheckedChange={handleAllCheck}
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63] data-[state=indeterminate]:bg-[#e91e63] data-[state=indeterminate]:border-[#e91e63]"
/>
<label
htmlFor="terms-all"
className="text-sm font-semibold cursor-pointer"
>
전체 동의
</label>
</div>
<div className="pl-6 space-y-3">
<div className="flex items-start space-x-2">
<Checkbox
id="terms-service"
checked={agreements.termsOfService}
onCheckedChange={(checked) =>
setAgreements({
...agreements,
termsOfService: checked as boolean,
})
}
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<div className="flex-1">
<label htmlFor="terms-service" className="text-sm cursor-pointer">
<span className="font-medium text-[#e91e63]">[필수]</span> 서비스
이용약관
</label>
<button className="text-xs text-gray-500 underline ml-1">
보기
</button>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="terms-privacy"
checked={agreements.privacyPolicy}
onCheckedChange={(checked) =>
setAgreements({
...agreements,
privacyPolicy: checked as boolean,
})
}
className="mt-1 data-[state=checked]:bg-[#e91e63] data-[state=checked]:border-[#e91e63]"
/>
<div className="flex-1">
<label htmlFor="terms-privacy" className="text-sm cursor-pointer">
<span className="font-medium text-[#e91e63]">[필수]</span>{" "}
개인정보 처리방침
</label>
<button className="text-xs text-gray-500 underline ml-1">
보기
</button>
</div>
</div>
<div className="flex items-start space-x-2">
<Checkbox
id="terms-marketing"
checked={agreements.marketing}
onCheckedChange={(checked) =>
setAgreements({ ...agreements, marketing: checked as boolean })
}
className="mt-1 data-[state=checked]:bg-[#03a9f4] data-[state=checked]:border-[#03a9f4]"
/>
<div className="flex-1">
<label htmlFor="terms-marketing" className="text-sm cursor-pointer">
<span className="font-medium text-gray-600">[선택]</span> 마케팅
정보 수신 동의
</label>
<p className="text-xs text-gray-500 mt-1">
이벤트, 프로모션, 쿠폰 등의 혜택 정보를 받을 수 있습니다
</p>
</div>
</div>
</div>
<button
disabled={!allRequired}
className="w-full mt-4 bg-[#e91e63] text-white py-2 px-4 rounded-md hover:bg-[#c2185b] disabled:bg-gray-300 disabled:cursor-not-allowed"
>
예약하기
</button>
</div>
);
}예약 필터 (상태별 선택)
import { Checkbox } from "@vortex/ui-cals";
import { useState } from "react";
export default function ReservationStatusFilter() {
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([
"available",
"pending",
"confirmed",
]);
const statuses = [
{ id: "available", label: "예약 가능", color: "#4caf50", count: 15 },
{ id: "pending", label: "승인 대기", color: "#ff9800", count: 3 },
{ id: "confirmed", label: "예약 확정", color: "#03a9f4", count: 8 },
{ id: "cancelled", label: "예약 취소", color: "#f44336", count: 2 },
{ id: "completed", label: "서비스 완료", color: "#9c27b0", count: 12 },
];
return (
<div className="space-y-3 max-w-sm">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900">예약 상태 필터</h3>
<button
onClick={() => setSelectedStatuses([])}
className="text-xs text-[#e91e63] hover:underline"
>
전체 해제
</button>
</div>
<div className="space-y-2">
{statuses.map((status) => (
<div key={status.id} className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox
id={status.id}
checked={selectedStatuses.includes(status.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedStatuses([...selectedStatuses, status.id]);
} else {
setSelectedStatuses(
selectedStatuses.filter((id) => id !== status.id)
);
}
}}
style={{
["--checkbox-bg" as string]: status.color,
}}
className="data-[state=checked]:bg-[var(--checkbox-bg)] data-[state=checked]:border-[var(--checkbox-bg)]"
/>
<label
htmlFor={status.id}
className="text-sm cursor-pointer flex items-center gap-2"
>
<span
className="w-2 h-2 rounded-full"
style={{ backgroundColor: status.color }}
/>
<span>{status.label}</span>
</label>
</div>
<span className="text-xs text-gray-500 font-medium">
{status.count}건
</span>
</div>
))}
</div>
<div className="pt-3 border-t border-gray-200">
<p className="text-xs text-gray-600">
선택된 상태:{" "}
<span className="font-semibold">{selectedStatuses.length}개</span>
</p>
</div>
</div>
);
}관련 컴포넌트
Last updated on