Skip to Content
ComponentsCalsFormCheckbox

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과의 차이점

속성FoundationiCignalCals
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 backgroundBlue backgroundPink background (Primary)
그룹 선택기본 그룹화필터 그룹화서비스 옵션, 약관 계층 구조

사용 시나리오 비교

Foundation Checkbox: 범용 다중 선택, 설정 토글, 항목 선택 iCignal Checkbox: 데이터 필터, 차트 시리즈 선택, 분석 옵션 Cals Checkbox: 부가 서비스 선택, 약관 동의, 알림 설정, 예약 옵션

Props API

PropTypeDefaultDescription
checkedboolean | 'indeterminate'false체크 상태
defaultCheckedbooleanfalse기본 체크 상태 (비제어 컴포넌트)
onCheckedChange(checked: boolean) => void-체크 상태 변경 이벤트 핸들러
disabledbooleanfalse비활성화 여부
requiredbooleanfalse필수 선택 여부
namestring-Form name 속성
valuestring'on'Form value 속성
idstring-HTML id 속성 (label 연결용)
classNamestring-추가 CSS 클래스

접근성

Checkbox 컴포넌트는 웹 접근성 표준을 준수합니다:

  • Radix UI 기반: WAI-ARIA 1.2 Checkbox 패턴 구현
  • 시맨틱 마크업: role="checkbox" 및 적절한 ARIA 속성
  • 키보드 네비게이션:
    • Space: 체크박스 토글
    • Tab: 다음 체크박스로 이동
    • Shift + Tab: 이전 체크박스로 이동
  • 스크린 리더: aria-checked 상태 안내
  • Label 연결: htmlForid를 통한 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> ); }

관련 컴포넌트

  • Input - 텍스트 입력 필드
  • Select - 드롭다운 선택 입력
  • Radio - 단일 선택 입력
  • Switch - 토글 스위치
  • Button - 폼 제출 버튼
Last updated on