8.8 KiB
Checkbox
현재 HM 구현의 기준 문서. 공통 원칙은 docs/guidelines/프로젝트-지침.md(§6, §7), Token 구조 원칙은 테마-토큰-가이드.md를 따른다. 이 문서는 Checkbox 고유의 API·Token·상태·결정만 기록한다.
1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Checkbox (Radix Checkbox) |
| 대표 Story | Components/Form/Checkbox → All (이름 "Checkbox") |
| 보류 | 아래 §11 |
2. 적용 근거
- 참고 근거: ui-viz 기존 테스트 규격에는 Checkbox 문서가 없다(border·radius·indicator는 TBD, mark는 native UA 소유, HOLD). 구현 값으로 쓸 수 있는 확정 규격이 없어 참고만 했다.
- 현재 HM 규격: 아래 §3~§4. 현재 HM Semantic 체계(surface, stroke, action-primary, state-active, on-state-active)로 새로 정했다.
- 구현 방식: shadcn / Radix Checkbox 구조(Root/Indicator,
data-state, indeterminate, 키보드, Label 연결, 폼 동작)를 유지하고 theme CSS로 HM 스타일을 연결. - 보류: §11.
3. 기본 규격 (현재 HM 규격)
| 항목 | 값 | 비고 |
|---|---|---|
| Box | 18 × 18px | 현재 HM |
| Border | 1px --hm-sem-stroke-default |
Semantic |
| Radius | --hm-radius-control-md (4px) |
공통 control radius |
| Face | --hm-sem-bg-surface |
Semantic |
| Indicator | shadcn CheckIcon / MinusIcon, 14px, --hm-sem-on-state-active |
shadcn 아이콘 유지 |
| Hit area | 18px box + 연결된 Label 클릭 |
별도 확장 없음 |
shadcn(lucide) 아이콘을 유지했다. 별도 HM glyph는 만들지 않았다.
4. 상태 규격
| 상태 | 값 |
|---|---|
| Unchecked | 위 기본 규격 |
Checked (data-state="checked") |
border·face --hm-sem-state-active, 체크 아이콘 |
Indeterminate (data-state="indeterminate") |
Checked와 같은 면, 마이너스 아이콘 |
| Hover (미체크) | border --hm-sem-action-primary |
| Hover (체크·indeterminate) | Black 20% overlay (--hm-color-action-overlay-hover) |
Focus (:focus-visible) |
shadcn ring (ring-3 ring-ring/50) 유지 |
Error (aria-invalid="true") |
border --hm-sem-action-destructive |
| Disabled | opacity: 0.6 (이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), cursor: not-allowed |
5. API
Radix Checkbox props 그대로 (checked / defaultChecked / onCheckedChange, "indeterminate", disabled, required, name, value, form). 추가 prop 없음. 출력 속성: data-slot="checkbox", Radix data-state·data-disabled.
- Label과의 연결은
<Label htmlFor>또는 감싸기. Label·Helper·Error 조합은 소비 조합(field.md).
Form 연결 및 Reset
- Form 내부에서는 기존 native Form 연결을 사용한다. 외부에서는
form="target"과<form id="target">으로 연결한다. - 기본 버튼 렌더링에
form을 지정하면 HM은 Radix의 공개asChild경로로 같은 Checkbox 버튼에도form을 전달한다. 추가 DOM wrapper, 별도 상태, Reset listener 또는 숨김 input 강제 조작은 없다. 호출자가 명시한asChild경로는 기존대로 유지한다. - Uncontrolled는
defaultChecked로 초기 상태를 지정한다. Native Reset 버튼과form.reset()은 Radix가 보관한 최초 상태를 복원한다. 이후defaultChecked변경을 현재 선택값 변경으로 사용하지 않는다. - Controlled는 부모의
checked가 상태의 기준이다. 부모onReset에서 초기값을 복원하고onCheckedChange로 변경 요청을 반영한다. 외부 Form에도 내부 Form과 같은 Radix Reset callback 계약이 적용된다. - Checked는
name/value로 제출된다(기본value="on"). Unchecked와 indeterminate는 제출되지 않는다. Indeterminate는aria-checked="mixed"이며 native input의checked는 false다. Disabled는 버튼과 숨김 input 모두 비활성화되어 제출되지 않는다.
const initialChecked = true
const [checked, setChecked] = React.useState(initialChecked)
return (
<>
<form id="target" onReset={() => setChecked(initialChecked)}>
<button type="reset">초기화</button>
</form>
<Checkbox
form="target"
name="agreement"
checked={checked}
onCheckedChange={setChecked}
/>
</>
)
Uncontrolled 예제는 checked / onCheckedChange 대신 defaultChecked={true}를 사용하며 부모의 Reset 상태 관리는 필요하지 않다.
6. Token
Component Mapping 위치: packages/ui/src/theme/checkbox.css (--hm-checkbox-*).
size, radius, border-width, icon-size, background, border-color, hover-border-color, checked-background, checked-border-color, checked-hover-overlay, indicator-color, error-border-color. Semantic(--hm-sem-*)을 참조하고 Input 등 다른 컴포넌트 Token을 쓰지 않는다. Primitive 신규 추가 없음(--hm-sem-action-destructive는 기존 Primitive를 직접 참조).
7. shadcn / Radix에서 유지한 항목
Radix 구조·ARIA(role=checkbox, aria-checked)·키보드, data-slot, cn(..., className), transition-colors, outline-none + focus ring, disabled 커서, lucide CheckIcon.
8. HM에서 변경한 항목
- shadcn
size-4 rounded-[4px] border shadow-xs,dark:및data-[state=checked]:bg-primary계열 class 제거 → theme CSS. MinusIcon추가(shadcn 기본은 indeterminate 아이콘이 없다). 두 아이콘은data-state에 따라 CSS로 전환.- Disabled opacity 50 → 60.
9. Storybook
- 대표 Story
All("Checkbox"):unchecked,checked,indeterminate,interactive(controlled),hover,focus,error,disabled,long. Focus는play로 실제 focus. - verification:
Unchecked,Checked,Indeterminate,Disabled,Error,Playground.
10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
11. 미확정 / 보류
- Checkbox Group 간격, group Label·Error 슬롯, option label 전경 role: 미결정.
- 수치(18px, 14px 아이콘 등)는 현재 HM 규격이며 디자인 확정 시 재검토.
- Checkbox Group, Error 문구 슬롯: 컴포넌트 없음(소비 조합).
- Hit area 확장(24px 타깃 등) 미적용.
- Disabled opacity
0.6: 이 컴포넌트 값이며 전역 Disabled 규칙으로 승격하지 않는다.
12. 관련 파일
packages/ui/src/components/ui/checkbox.tsx
packages/ui/src/theme/checkbox.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export Checkbox)
src/components/checkbox.stories.tsx
13. 검증 상태
npm run typecheck,npm run build: 통과(2026-10-02).- Storybook computed style: 18×18, border 1px
rgb(154,146,132), radius 4px; Checked/Indeterminate bg·borderrgb(18,51,40); Error borderrgb(211,47,47); Disabled opacity 0.6; 체크/마이너스 아이콘이 상태에 따라 하나만 표시. - 실제 interaction: controlled indeterminate → click → checked → click → unchecked, Label 클릭으로 토글 확인.
- 2026-10-08, React 19.3 / Radix Checkbox 1.3.11: 프로젝트 외부 격리 환경의 수정된 HM 패키지로 실제 브라우저 검증. 수정 전 외부 Form의 true·false·indeterminate Reset 불일치를 재현했고, 수정 후 모두 PASS.
- Form 내부·외부에서 Space로 상태 변경 후 Native Reset 버튼 및
form.reset()을 실행:defaultCheckedtrue·false·indeterminate와 Controlled 부모onReset의 상태·ARIA·숨김 input·FormData 복원 확인. Indeterminate 복원 시 숨김 input은checked=false,indeterminate=true, 제출값 없음. - 기존 Radix 동작도 구분해 확인: 최초 indeterminate 렌더에서는 숨김 input의
indeterminate속성이 false다(내부·외부 및 수정 전·후 동일). UI/ARIA는 mixed, input의checked=false, FormData 제외는 정상이며 이번 외부 Form Reset 수정에서 이 초기화 동작은 변경하지 않는다. - Native Form HTTP POST 8건: 내부·외부·Controlled의 Checked 값은
choice=accepted, Unchecked·indeterminate·Disabled 값은 제외.requirednative 유효성 및 ARIA, Disabled Tab 제외, BUTTON ref 유지 확인. - 수정 전·후 같은 Theme에서 Checked·Unchecked·Indeterminate·Disabled computed style과 아이콘 일치: 18×18px, border 1px, radius 4px, icon 14px, Disabled opacity 0.6. 키보드 focus-visible 3px ring도 일치. CSS·Theme·대표 Story 변경 없음.
- UI 전체 타입 검사 및 공식 UI 패키지 빌드를 격리 경로에서 통과. 테스트에 사용한 Checkbox 소스와 실제 수정본의 SHA-256 일치. 공용 dist는 이 검증에서 빌드하지 않음.
- 미확인: 실제 pointer hover, 사이드바 화면, 390px 폭. Hover 관련 CSS와 class는 변경하지 않음.