Groo Design

Checkbox

사용자가 하나 이상의 옵션을 고르게 하는 컴포넌트. 여러 항목 가운데 여러 개를 고르거나, 저장하기 전에 켜고 끌 것을 모아 둘 때 쓴다.

구조는 당근 SEED Checkbox(Apache-2.0)를 따른다 — 칸(Checkmark) · 칸 + 라벨(Checkbox) · 묶음(Checkbox Group). 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-09-30 사용자 결정).

수치 원본은 checkbox.yaml 이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

Square · neutral
Square · brand
Ghost · neutral
Square · neutral
Square · brand
Ghost · neutral
선택 안 됨 · 선택 · 일부 선택 — Square 의 neutral · brand 와 Ghost, 위는 라이트 아래는 다크

직접 골라 보기

크기 · 모양 · 톤 · 굵기 · 상태를 고르면 스펙대로 그린 Checkbox 와 그 코드가 바뀐다. 칸은 실제로 눌러 볼 수 있다.

크기 size
모양 shape
톤 tone
굵기 weight
처음 상태
비활성
모드
브랜드
import { Checkbox } from "@/components/ui/checkbox"

<Checkbox defaultChecked label="단종된 카드도 보기" />

Anatomy

ⓐⓑⓒ라벨ⓓ
ⓐ Checkmarkⓑ Iconⓒ Labelⓓ Focus ring
Checkbox 는 칸(Checkmark)과 라벨로 이뤄진다 — 칸은 따로 떼어 목록 행에 넣어 쓸 수 있다
부위설명
ⓐ Checkmark칸 — 배경 · 테두리 · 모양. 체크 · 가로줄 아이콘이 들어간다. 따로 떼어 목록 행 · 표 머리에 쓴다.
ⓑ Icon체크(선택) · 가로줄(일부 선택). lucide Check · Minus, 선 3.
ⓒ Label무엇을 고르는지. 칸과 함께 눌린다.
ⓓ Focus ring키보드 포커스에만 2px 링 · 2px 띄움(v106).

부위

부위설명
rootCheckbox 한 줄 — 칸 + 라벨, 누르는 영역
checkmark칸(Checkmark) — 배경 · 테두리 · 모양. 목록 행에는 이것만 넣어 쓴다
icon체크 · 가로줄(lucide Check · Minus)
label글자(Label)
focusRing키보드 포커스 링(웹 · 앱 — 키보드로 옮길 때만)
group묶음(Checkbox Group) — 세로로 쌓는다

Properties

Size

두 가지다. 칸 · 라벨 · 줄 높이가 함께 정해진다.

  • medium(칸 20 · 라벨 14 · 줄 32) — 기본. 화면 안의 목록 · 설정.
  • large(칸 24 · 라벨 16 · 줄 36) — 한 화면의 중심 선택, 모바일에서 홀로 서는 선택.

누르는 영역은 라벨까지 묶어 가로 · 세로 44 까지 넓힌다(기초 Inclusive — 44 는 반드시).

medium칸 20 · 라벨 14px · 줄 32
large칸 24 · 라벨 16px · 줄 36
크기 두 가지 — 칸 · 라벨 · 줄 높이

크기

size최소 높이checkmark 크기글자설명
medium (기본)32px20pxtext-t4 14px / 19px칸 20 · 라벨 14 — 기본
large36px24pxtext-t5 16px / 22px칸 24 · 라벨 16 — 한 화면의 중심 선택 · 모바일 단독

칸 모양마다 아이콘 크기가 다르다 — Ghost 는 칸이 없어 아이콘이 크다.

크기별 아이콘 — Square

속성medium (기본)large
아이콘12px14px

크기별 아이콘 — Ghost

속성medium (기본)large
checkmark 배경transparenttransparent
checkmark 테두리 두께0px0px
아이콘14px18px
icon 색color-fg-placeholder #767C8B · 다크 #838997 — 선택 안 됨도 옅은 체크로 보인다color-fg-placeholder #767C8B · 다크 #838997 — 선택 안 됨도 옅은 체크로 보인다

모든 조합에 공통인 값:

공통

부위상태속성값비고
rootenabled간격spacing-x2 8px
rootenabled커서pointer
rootenabled줄 맞춤flex-start줄은 칸 + 라벨만큼만 차지한다 — 묶음 폭으로 늘이지 않는다(직접 짠 줄은 묶음 폭을 쓴다)
rootenabled터치 영역44 × 44라벨까지 묶어 44 — 목록 행에서 칸만 쓰면 줄 전체가 누르는 영역(SEED touch target · 기초 Inclusive)
checkmarkenabled모서리radius-r1 4px
checkmarkenabled전환 대상background-color, border-color, color
checkmarkenabled전환 시간motion-duration-color-transition 150ms
checkmarkenabled전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
iconenabled글리프Checklucide Check · 선 3
labelenabled글꼴font-sans
labelenabled글자·아이콘color-fg-neutral #1A1F2E · 다크 #F5F6FA
groupenabled간격spacing-x3 12px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4)
focusRingfocused링 두께2px웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)
focusRingfocused링 간격2px
focusRingfocused링 색color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
checkmarkpressed배율세로 2px 축소칸만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)
checkmarkpressed축소 시간motion-duration-pressed-scale 150ms
checkmarkpressed축소 곡선motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)
rootdisabled커서not-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)
labeldisabled글자·아이콘color-fg-disabled #8A91A0 · 다크 #838997

Weight

라벨 굵기다. 강조하거나 묶음의 부모처럼 한 단계 위에 설 때 bold.

regular라벨 400(기본)
bold라벨 700 — 강조 · 묶음의 부모
라벨 굵기 — regular · bold

굵기

weight굵기설명
regular (기본)400라벨 400(기본)
bold700라벨 700 — 강조 · 묶음의 부모

Tone

선택했을 때의 색이다. neutral(짙은 회색)이 기본이고, 브랜드 색(brand)은 서비스 핵심 흐름에서만 쓴다 — 버튼에서 정한 "브랜드 색은 꼭 필요한 곳에만" 과 같다. Radio · Switch 도 같은 규칙이다.

neutral기본 — 짙은 회색
brand · Desk서비스 핵심 흐름에서만
brand · HRHR 은 초록
톤 두 가지 — neutral · brand(Desk · HR)

Shape

  • square — 칸 + 체크. 여러 개를 고르는 목록, 사용자가 알고 골라야 하는 선택(기본).
  • ghost — 칸 없이 체크만. 선택 안 됨도 옅은 체크로 보인다. 필수가 아니고 셋 이하일 때.

Desk 할 일 완료도 Checkbox square 다 — 옛 동그라미 체크는 따로 두지 않는다(2026-10-23 할 일 결정 — 할 일 줄 앞 Checkbox).

square칸 + 체크
ghost칸 없이 체크만
모양 두 가지 — Square · Ghost

State

체크 여부(선택 안 됨 · 선택 · 일부 선택)와 상호작용 상태가 곱해진다.

상태모습
enabled기본
hovered웹. 누름 색과 같다(v106), 축소는 없다
focused웹 · 앱. 키보드 포커스에만 링 2px · 띄움 2px(v106). 앱은 하드웨어 키보드로 옮길 때만 그리고 손가락으로 누를 때는 없다(2026-10-08)
pressed누름 색 + 칸 세로 2px 거리 축소(v104). 라벨은 줄지 않는다
disabled전용 색(bg-disabled · fg-disabled, v106). 불투명도로 흐리게 하지 않는다
Square · neutral
기본
enabled
호버
hovered
포커스
focused
누름
pressed
비활성
disabled
선택 안 됨
선택
일부 선택
Square · brand
기본
enabled
호버
hovered
포커스
focused
누름
pressed
비활성
disabled
선택 안 됨
선택
일부 선택
Ghost · neutral
기본
enabled
호버
hovered
포커스
focused
누름
pressed
비활성
disabled
선택 안 됨
선택
일부 선택
체크 여부 × 상태 — 호버 · 포커스 · 누름은 그 순간을 멈춰 그렸다

상태 매트릭스 — Square · 선택 안 됨

상태커서checkmark 배경checkmark 테두리checkmark 테두리 두께checkmark 배율checkmark 축소 시간checkmark 축소 곡선icon 글리프label 글자·아이콘링 두께링 간격링 색
enabled 기본pointertransparentcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———none — 선택 안 된 칸에는 아이콘이 없다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-layer-default-pressed #F7F8FD · 다크 #353B4Dcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———none — 선택 안 된 칸에는 아이콘이 없다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointertransparentcolor-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px———none — 선택 안 된 칸에는 아이콘이 없다color-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 칸 세로 2px 거리 축소(v104)pointercolor-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 불투명 짝color-stroke-neutral-solid #767C8B · 다크 #838997 — 3:1 이상 — v109(SEED 는 옅은 선)1px세로 2px 축소 — 칸만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)none — 선택 안 된 칸에는 아이콘이 없다color-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)color-bg-disabled #F5F6FA · 다크 #353B4Dcolor-stroke-neutral-weak #E5E8EF · 다크 #404757 — SEED stroke.neutral-muted 의 불투명 짝1px———none — 선택 안 된 칸에는 아이콘이 없다color-fg-disabled #8A91A0 · 다크 #838997———

상태 매트릭스 — Square · neutral · 선택

상태커서checkmark 배경checkmark 테두리 두께checkmark 배율checkmark 축소 시간checkmark 축소 곡선icon 색label 글자·아이콘링 두께링 간격링 색
enabled 기본pointercolor-bg-neutral-inverted #1A1F2E · 다크 #F5F6FA0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointercolor-bg-neutral-inverted #1A1F2E · 다크 #F5F6FA0px———color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 칸 세로 2px 거리 축소(v104)pointercolor-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC0px세로 2px 축소 — 칸만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)color-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)color-bg-disabled #F5F6FA · 다크 #353B4D0px———color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997———

상태 매트릭스 — Ghost · neutral · 선택

상태커서checkmark 배경checkmark 테두리 두께checkmark 배율checkmark 축소 시간checkmark 축소 곡선아이콘icon 색label 글자·아이콘링 두께링 간격링 색
enabled 기본pointertransparent0px———14pxcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
hovered 웹 — 누름 색과 같다(v106), 축소는 없다pointercolor-bg-neutral-weak #F5F6FA · 다크 #353B4D0px———14pxcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
focused 키보드 포커스에만 링 — 웹 · 앱(앱은 2026-10-08 부터, 손가락으로 누를 때는 없다)pointertransparent0px———14pxcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA2px — 웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 누름 색 + 칸 세로 2px 거리 축소(v104)pointercolor-bg-neutral-weak #F5F6FA · 다크 #353B4D — SEED palette.gray-2000px세로 2px 축소 — 칸만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)14pxcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA———
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다not-allowed — 포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)transparent0px———14pxcolor-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997———
직접 눌러 보기 — 칸이나 라벨을 누르면 바뀐다(Tab 으로 포커스)

모션

전환시간곡선대상비고
선택 안 됨 ↔ 선택 ↔ 일부 선택motion-duration-color-transition 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)background, border, icon color칸의 채움 · 테두리 · 아이콘 색이 함께 바뀐다(SEED Checkmark 와 같다) — 아이콘은 커지거나 줄지 않는다
누름motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)scale칸만 세로 2px 거리 축소 — 라벨은 줄지 않는다(v104)

Group

여러 항목을 묶어 세로로 쌓는다. 줄 사이는 12 다 — 줄 높이 32 · 36 에 더하면 44 · 48 마다 한 줄이 서서, 묶음 안에서도 줄마다 누르는 영역 44 를 온전히 받는다(사용자 결정). 줄은 칸 + 라벨만큼만 차지하고 묶음 폭으로 늘이지 않는다. 부모 Checkbox 를 맨 위에 둘 수 있다 — 부모를 고르면 자식이 모두 선택되고, 자식을 일부만 고르면 부모는 일부 선택(가로줄)이 된다.

묶음 — 부모를 눌러 보거나 자식을 하나씩 눌러 보면 부모가 따라 바뀐다

묶음

속성값비고
간격spacing-x3 12px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4)

Guidelines

누르는 영역

라벨을 포함한 줄 전체가 누르는 영역이다 — 위아래로 44 까지 넓히고, 묶음에서는 줄 사이 12 가 이 영역이 서로 겹치지 않게 한다. 목록처럼 칸(Checkmark)만 행에 넣어 쓸 때는 행 전체가 눌려야 한다.

Checkbox칸 + 라벨이 한 영역
월급+3,200,000원
점심 식사−12,000원
버스−1,500원
목록 행칸만 쓰면 행 전체가 누르는 영역
누르는 영역(분홍) — Checkbox 는 칸 + 라벨, 목록 행은 행 전체

묶음 쓰기

항목이 여럿이면 묶음으로 둔다. 모두를 한 번에 고를 일이 있으면 부모를 맨 위에 둔다.

내보낼 데이터
아무것도 안 고름부모는 선택 안 됨
내보낼 데이터
모두 고름부모도 선택
내보낼 데이터
일부만 고름부모는 일부 선택(가로줄)
Desk 데이터 내보내기 — 아무것도 · 모두 · 일부 골랐을 때의 부모

모양 고르기

필수가 아니고 셋 이하면 ghost, 필수이거나 사용자가 알고 골라야 하면 square 다.

보기
이렇게 — 필수가 아니고 셋 이하 — 가벼운 보기 옵션은 Ghost
내보낼 데이터(하나 이상)
이렇게 하지 않는다 — 하나 이상 꼭 골라야 하는 긴 목록에 Ghost — 고른 것과 안 고른 것이 잘 안 갈린다
모양 고르기

선택 색

선택 색은 짙은 회색이 기본이다. 브랜드 색은 서비스 핵심 흐름에만 — 체크가 많은 화면에 브랜드 색을 깔면 브랜드 색 버튼이 설 자리가 없어진다.

9:41
설정
데이터 내보내기
내보낼 데이터
이렇게 — 체크는 짙은 회색 — 화면의 주 액션(내보내기)이 먼저 읽힌다
9:41
설정
데이터 내보내기
내보낼 데이터
이렇게 하지 않는다 — 체크마다 브랜드 색을 깔면 브랜드 색이 흩어진다
선택 색

오류

칸 모양은 바꾸지 않는다. 묶음 아래에 무엇을 해야 하는지 글로 알린다(사용자 결정 — SEED 와 같다). 오늘 제품의 체크 오류는 모두 묶음 단위(하나 이상 고르기)다. 묶음을 Field 로 감싸면 그 꼬리가 오류 글 자리이고, 라벨은 묶음의 이름으로 이어진다(2026-10-01). Field 의 오류 · 막힘도 칸에 닿는다 — 오류면 칸마다 aria-invalid, 막히면 묶음 aria-disabled 와 칸마다 disabled 다(2026-10-08). 묶음(role="group")에는 aria-invalid · aria-required 를 달지 않는다 — 그 역할은 받지 않는다(SEED 와 같다).

내보낼 데이터
내보낼 데이터를 하나 이상 골라 주세요.
이렇게 — 칸은 그대로, 묶음 아래 글로 무엇을 할지 알린다
내보낼 데이터
내보낼 데이터를 하나 이상 골라 주세요.
이렇게 하지 않는다 — 칸마다 빨간 테두리 — 묶음 오류에서 모든 칸이 빨개져 무엇이 틀렸는지 흐려진다
오류는 묶음 아래 글로

Checkbox 와 Switch

둘 다 켜고 끄는 선택을 보인다.

CheckboxSwitch
값이 적용될 때저장 같은 액션을 해야 적용(권장)누르는 순간 적용 — 저장해야 적용되는 값에는 쓰지 않는다
항목 구성한 묶음에 여러 항목항목마다 따로
하위 항목부모가 모두를 고르고 풀 수 있다부모와 하위 사이 관계 없음 — 위가 꺼지면 아래를 막는다(값은 그대로)
경계 사례고정 금액으로 기록(켜면 금액 칸이 나와도 저장 때 반영) · 자동 기록 · 상단에 고정결제 알림 · 단종 카드 포함(누르면 목록이 바로 바뀐다) · 일정의 종일(저장 폼 안이어도 날짜 칸이 바로 바뀐다)

이 표는 Checkbox 와 Switch 의 한 벌이다 — Switch 문서는 이 표를 가리킨다. 결정(누르는 순간 바뀌는 것만 Switch — SEED 와 같은 강도)은 그대로이고, 이관 때 고를 경계 사례 한 줄을 더했다(2026-10-22 사용자 결정 7B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym). 다른 고르는 컴포넌트와의 표는 Select 의 "고르는 컴포넌트 고르기".

9:41
알림
결제일 하루 전
예산 초과
할 일 마감
Switch누르는 순간 적용 — 항목마다 따로
9:41
설정
데이터 내보내기
내보낼 데이터
Checkbox골라 두고 내보내기로 적용 — 한 묶음에 여러 항목
저장해야 적용되는 폼은 Checkbox, 바로 적용되는 설정은 Switch

코드

레시피 recipes/shadcn/components/ui/checkbox.tsx 를 쓴다 — Checkbox(칸 + 라벨) · Checkmark(칸) · CheckboxGroup(묶음). 아래 미리보기는 스펙 값으로 그린 모습이다.

기본

기본 — medium · square · neutral
import { Checkbox } from "@/components/ui/checkbox"

<Checkbox label="단종된 카드도 보기" />

크기 · 굵기

크기 · 굵기
<Checkbox size="medium" label="medium" />
<Checkbox size="large" label="large" />
<Checkbox size="large" weight="bold" label="large · bold" />

모양 · 톤

모양 · 톤
<Checkbox defaultChecked label="neutral" />
<Checkbox defaultChecked tone="brand" label="brand" />
<Checkbox defaultChecked shape="ghost" label="ghost" />

묶음 · 일부 선택

묶음 · 일부 선택
const all = ["tx", "budget", "memo"] as const
const [picked, setPicked] = useState<string[]>(["tx"])
const parent = picked.length === all.length ? true : picked.length ? "indeterminate" : false

<CheckboxGroup aria-label="내보낼 데이터">
  <Checkbox weight="bold" label="전체" checked={parent}
    onCheckedChange={(v) => setPicked(v === true ? [...all] : [])} />
  <Checkbox label="거래 내역" checked={picked.includes("tx")}
    onCheckedChange={(v) => setPicked((p) => (v ? [...p, "tx"] : p.filter((x) => x !== "tx")))} />
  {/* 예산 · 메모도 같은 방식 */}
</CheckboxGroup>

비활성

비활성
<Checkbox disabled label="이 카드 기억하기" />
<Checkbox disabled defaultChecked label="이 카드 기억하기" />

칸만(목록 행)

목록 줄은 List 의 ListCheckItem 을 쓴다 — 줄의 여백 · 글자 · 누름이 정해져 있다. 표처럼 행을 따로 짜야 할 때만 아래처럼 행을 <label> 로 감싸 행 어디를 눌러도 선택되게 하고, group/checkbox 를 달아 행을 누르거나 올려도 칸이 누름 색 · 축소로 반응하게 한다.

월급+3,200,000원
점심 식사−12,000원
칸만 — 행 전체가 누르는 영역
import { Checkmark } from "@/components/ui/checkbox"

<label className="group/checkbox flex cursor-pointer items-center gap-x3 px-x6 py-x3">
  <Checkmark checked={selected} onCheckedChange={setSelected} aria-label="9월 25일 월급 선택" />
  <span className="flex-1">월급</span>
  <span>+3,200,000원</span>
</label>

Behavior

인터랙션동작
Click / Tap(칸 · 라벨)선택 ↔ 선택 안 됨. 일부 선택이면 선택으로. disabled 면 무시.
Keyboard Space포커스 상태에서 누르기와 같다.
Keyboard Enter폼을 제출한다 — 진짜 <input type="checkbox"> 처럼 폼의 기본 버튼(첫 제출 버튼)을 누른다. 칸은 바뀌지 않는다. 기본 버튼이 없거나 막혀 있으면 아무것도 하지 않는다. Radix 는 Enter 를 막기만 해서 레시피가 연다(사용자 결정 2026-10-08 — SEED 와 같다). 칸만(Checkmark) 쓰는 자리 · List 의 체크 줄 · Select Box 여럿 고르기도 같다.
Keyboard Tab다음 포커스로. 묶음 안의 항목도 하나씩 들어간다.
부모(일부 선택)누르면 자식을 모두 선택. 다 선택이면 모두 해제.
Disabled누르기 · 키보드 불가, 포커스에서 빠진다. 묶음에 disabled 를 주거나 막힌 Field 로 감싸면 칸이 모두 막힌다(묶음 aria-disabled).

Form 안 — <input type="checkbox">(또는 Radix 의 숨은 input)는 폼 제출에 들어간다. 여러 개의 결과는 배열이다. 검증은 onSubmit 또는 묶음을 떠날 때 — 누르는 동안 오류를 띄우지 않는다.

Accessibility

기준검증
WCAG 1.4.3 Color contrast(라벨 ≥ 4.5:1)라벨 fg-neutral × 흰 표면 16:1+ ✓
WCAG 1.4.11 Non-text contrast(UI ≥ 3:1)선택 안 된 칸 테두리 stroke-neutral-solid × 표면 4.2:1(다크 4.1:1) ✓ · 선택 채움 bg-neutral-inverted 16:1+ ✓ · Ghost 선택 안 됨은 fg-placeholder 4.18:1(다크 4.13:1) ✓ — v119 에서 gray-600 으로 옅어졌다(2026-10-16 글자 역할 D7)
WCAG 2.4.7 Focus visible키보드 포커스에만 링 2px · 띄움 2px(stroke-focus-ring) — 앱도 키보드로 옮길 때만 같은 링(2026-10-08)
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)칸 20 · 24 + 라벨까지 묶은 줄 32 · 36 ✓
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)라벨까지 묶어 위아래 44 로 넓힌다 ✓. 묶음 안에서도 줄 사이 12 라 줄마다 44 · 48 을 받는다(줄 사이가 4 면 이웃과 겹쳐 36 · 40 이 된다) — 목록 행에서 칸만 쓰면 행 전체
ARIArole="checkbox" + aria-checked="true · false · mixed"(mixed = 일부 선택). 라벨은 <label> 로 묶거나 aria-labelledby. 칸만 쓰면 aria-label 필수. 묶음은 <fieldset> + <legend>(또는 role="group" + aria-labelledby), 오류 글은 묶음에 aria-describedby. 오류면 칸마다 aria-invalid, 막히면 묶음 aria-disabled + 칸마다 disabled — role="group" 은 aria-invalid · aria-required 를 받지 않아 묶음에는 달지 않는다(Field 로 감싸면 레시피가 건다). 앱은 묶음에 이름 노드만 둔다 — 묶음 제목을 Semantics(label) 로 단 노드 하나, 묶음 역할(group)은 없다(Flutter 3.44 에 그 역할이 없다 — 2026-10-17 사용자 결정 03A-014 A).
Reduced motion모션 줄이기면 누름 축소를 빼고 색만 바꾼다(기초 Motion).

Do / Don't

✅ Do

  • 모든 Checkbox 에 라벨을 둔다(보이는 글자 또는 aria-label).
  • 여러 개 고르기는 Checkbox, 하나만 고르기는 Radio, 바로 적용되는 켜기 · 끄기는 Switch.
  • 부모 · 자식 묶음에서 일부만 고르면 부모는 일부 선택으로 둔다.
  • 목록 행에 칸만 넣으면 행 전체가 눌리게 한다.
  • 약관 보기 같은 링크는 라벨 옆이나 아래에 둔다 — 라벨은 고르는 글만.

❌ Don't

  • 바로 적용되는 켜기 · 끄기에 Checkbox — "알림 받기" 는 Switch 다.
  • 하나만 고르는 묶음에 Checkbox — Radio 를 쓴다.
  • 선택 색을 브랜드로 깔아 두기 — 브랜드 색은 핵심 흐름에서만.
  • 오류를 칸 색만으로 알리기 — 묶음 아래 글로 알린다.
  • 라벨 안에 링크 · 버튼 — 줄의 누르는 영역(44)이 덮어 눌리지 않는다. 라벨 옆이나 아래에 따로 둔다(사용자 결정 2026-10-08).

Specification

checkbox.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Checkbox 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.

Base

부위상태속성값
rootenabled간격gapspacing-x28px
줄 맞춤alignSelfflex-start줄은 칸 + 라벨만큼만 차지한다 — 묶음 폭으로 늘이지 않는다(직접 짠 줄은 묶음 폭을 쓴다)
커서cursorpointer
터치 영역touchTarget44 × 44라벨까지 묶어 44 — 목록 행에서 칸만 쓰면 줄 전체가 누르는 영역(SEED touch target · 기초 Inclusive)
checkmarkenabled모서리radiusradius-r14px
전환 대상transitionPropertybackground-color, border-color, color
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
iconenabled글리프glyphChecklucide `Check` · 선 3
labelenabled글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
글꼴fontFamilyfont-sansPretendard, Inter, sans-serif
groupenabled간격gapspacing-x312px묶음 안 줄 사이(세로) — 줄 32 · 36 에 12 를 더해 44 · 48 마다 한 줄이라 이웃 줄과 누르는 영역(44)이 겹치지 않는다(사용자 결정 — SEED 는 4)
focusRingfocused링 두께width2px웹 · 앱 같다 — 앱은 하드웨어 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다(2026-10-08 — DESIGN.md State · Button 과 같다)
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
링 간격offset2px
checkmarkpressed배율scale세로 2px 축소칸만 준다 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(Feedback 의 눌림 피드백, v104)
축소 시간scaleDurationmotion-duration-pressed-scale150ms
축소 곡선scaleEasingmotion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1)
rootdisabled커서cursornot-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)
labeldisabled글자·아이콘foregroundfg-disabled#8A91A0다크 #838997

size=medium

부위상태속성값
rootenabled최소 높이minHeight32px
checkmarkenabled크기size20px
labelenabled글자typographytext-t414px / 19px

size=large

부위상태속성값
rootenabled최소 높이minHeight36px
checkmarkenabled크기size24px
labelenabled글자typographytext-t516px / 22px

size=medium, shape=square

부위상태속성값
iconenabled아이콘size12px

size=large, shape=square

부위상태속성값
iconenabled아이콘size14px

size=medium, shape=ghost

부위상태속성값
iconenabled아이콘size14px

size=large, shape=ghost

부위상태속성값
iconenabled아이콘size18px

weight=regular

부위상태속성값
labelenabled굵기fontWeight400

weight=bold

부위상태속성값
labelenabled굵기fontWeight700

shape=square, checked=unchecked

부위상태속성값
checkmarkenabled배경backgroundtransparent
테두리borderColorstroke-neutral-solid#767C8B다크 #8389973:1 이상 — v109(SEED 는 옅은 선)
테두리 두께borderWidth1px
iconenabled글리프glyphnone선택 안 된 칸에는 아이콘이 없다
checkmarkhovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
checkmarkpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 불투명 짝
checkmarkdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4D
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757SEED stroke.neutral-muted 의 불투명 짝

shape=square, checked=checked

부위상태속성값
checkmarkenabled테두리 두께borderWidth0px
checkmarkdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4D
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=square, checked=indeterminate

부위상태속성값
checkmarkenabled테두리 두께borderWidth0px
iconenabled글리프glyphMinuslucide `Minus` · 선 3
checkmarkdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4D
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=square, tone=neutral, checked=checked

부위상태속성값
checkmarkenabled배경backgroundbg-neutral-inverted#1A1F2E다크 #F5F6FA
iconenabled색colorfg-neutral-inverted#FFFFFF다크 #242938
checkmarkhovered배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC
checkmarkpressed배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC

shape=square, tone=neutral, checked=indeterminate

부위상태속성값
checkmarkenabled배경backgroundbg-neutral-inverted#1A1F2E다크 #F5F6FA
iconenabled색colorfg-neutral-inverted#FFFFFF다크 #242938
checkmarkhovered배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC
checkmarkpressed배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC

shape=square, tone=brand, checked=checked

부위상태속성값
checkmarkenabled배경backgroundbg-brand-solidDesk#0147AD다크 #1049A4HR#075B3C다크 #1B583E
iconenabled색colorstatic-white#FFFFFF
checkmarkhovered배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49
checkmarkpressed배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49

shape=square, tone=brand, checked=indeterminate

부위상태속성값
checkmarkenabled배경backgroundbg-brand-solidDesk#0147AD다크 #1049A4HR#075B3C다크 #1B583E
iconenabled색colorstatic-white#FFFFFF
checkmarkhovered배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49
checkmarkpressed배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49

shape=ghost

부위상태속성값
checkmarkenabled배경backgroundtransparent
테두리 두께borderWidth0px
iconenabled색colorfg-placeholder#767C8B다크 #838997선택 안 됨도 옅은 체크로 보인다
checkmarkhovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
checkmarkpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=ghost, checked=indeterminate

부위상태속성값
iconenabled글리프glyphMinuslucide `Minus` · 선 3

shape=ghost, tone=neutral, checked=checked

부위상태속성값
iconenabled색colorfg-neutral#1A1F2E다크 #F5F6FA
checkmarkhovered배경backgroundbg-neutral-weak#F5F6FA다크 #353B4D
checkmarkpressed배경backgroundbg-neutral-weak#F5F6FA다크 #353B4DSEED palette.gray-200
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=ghost, tone=neutral, checked=indeterminate

부위상태속성값
iconenabled색colorfg-neutral#1A1F2E다크 #F5F6FA
checkmarkhovered배경backgroundbg-neutral-weak#F5F6FA다크 #353B4D
checkmarkpressed배경backgroundbg-neutral-weak#F5F6FA다크 #353B4D
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=ghost, tone=brand, checked=checked

부위상태속성값
iconenabled색colorfg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
checkmarkhovered배경backgroundbg-brand-weak-pressedDesk#D7E5FC다크 #1F3A69HR#D6E8DE다크 #214132
checkmarkpressed배경backgroundbg-brand-weak-pressedDesk#D7E5FC다크 #1F3A69HR#D6E8DE다크 #214132SEED palette.carrot-200 자리 — 브랜드 200
icondisabled색colorfg-disabled#8A91A0다크 #838997

shape=ghost, tone=brand, checked=indeterminate

부위상태속성값
iconenabled색colorfg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
checkmarkhovered배경backgroundbg-brand-weak-pressedDesk#D7E5FC다크 #1F3A69HR#D6E8DE다크 #214132
checkmarkpressed배경backgroundbg-brand-weak-pressedDesk#D7E5FC다크 #1F3A69HR#D6E8DE다크 #214132
icondisabled색colorfg-disabled#8A91A0다크 #838997

SEED 와 다른 점

  • 선택 안 된 칸의 테두리는 stroke-neutral-solid(3:1 이상, v109). SEED 는 옅은 선(stroke.neutral-weak)이지만 groo 는 칸을 선으로 알아봐야 한다(1.4.11).
  • 투명한 누름(SEED bg.transparent-pressed) · 비활성 테두리(stroke.neutral-muted)는 불투명한 가장 가까운 역할(bg-layer-default-pressed · stroke-neutral-weak)로 — 8자리 hex 를 lint 가 거부한다.
  • Ghost 의 누름 바탕(SEED palette.gray-200 · carrot-200)은 역할 색 bg-neutral-weak · bg-brand-weak-pressed 로.
  • 묶음 줄 사이는 12(사용자 결정) — SEED 는 4 다. 4 면 이웃 줄의 누르는 영역(44)과 겹쳐 한 줄이 실제로 36 · 40 만 받는다. groo 는 기초에서 44 를 반드시로 정했다(Inclusive Design).
  • 웹의 hovered · focused 를 더한다(v106). focused 는 앱의 키보드 포커스에도 그린다(2026-10-08).
  • 아이콘은 lucide(선 3) — SEED 는 채운 체크 아이콘.

Migration notes

2026-10-22 — Switch 와 견주는 표에 경계 사례

"Checkbox 와 Switch" 표에 경계 사례 한 줄(고정 금액 · 자동 기록 · 상단 고정은 Checkbox, 결제 알림 · 단종 카드 포함 · 일정 종일은 Switch)을 더하고 이 표를 한 벌로 — Switch 문서는 가리킨다(사용자 결정 7B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym). 결정 그대로, 값 변경 없음.

2026-10-08 — Enter · 묶음 상태 · 라벨 안 링크

앱 적용(desk-front #428 ~ #430 · desk-app #407 ~ #410)이 남긴 문제를 사용자가 비교 페이지에서 정했다.

  • Enter(B1) — 스펙은 "Enter 는 폼 제출에 둔다" 인데 Radix 는 Enter 를 막기만 해 아무 일도 없었다. 레시피의 칸(Checkmark)이 Enter 를 받아 폼의 기본 버튼을 누른다 — 진짜 input · SEED 와 같다. 칸은 바뀌지 않고, 고르기는 Space 다. Checkmark 를 쓰는 List 의 체크 줄 · Select Box 여럿 고르기도 같이 바뀌었다.
  • 묶음 상태(9) — Field 의 오류 · 막힘이 묶음에 닿지 않았다(막힌 Field 안의 칸이 눌렸다). 오류는 칸마다 aria-invalid, 막힘은 묶음 aria-disabled + 칸마다 disabled 로 건다. 필수는 묶음에 걸지 않는다(role="group" 은 받지 않는다) — 고르지 않고 내면 오류 글이 알린다. CheckboxGroup 에 disabled 를 더했다.
  • 라벨 안 링크(C1) — 줄의 누르는 영역이 라벨 안 링크를 덮는다. 레시피는 그대로 두고 Don't 에 적었다 — 링크는 라벨 옆 · 아래에.

2026-09-30 — 묶음 줄 사이 12 · 모션 표

Radio 를 옮기다 드러난 것을 Checkbox 에도 맞췄다(groo-design#143 뒤).

옛새
묶음 줄 사이 4(SEED 값) — 이웃 줄과 44 영역이 겹쳐 한 줄이 36 · 40 만 받았다줄 사이 12(spacing-x3) — 줄마다 44 · 48. 사용자 결정(기초의 "44 를 반드시")
줄 맞춤을 적지 않음 — 레시피는 줄을 묶음 폭으로 늘이고 미리보기는 내용만큼줄은 칸 + 라벨만큼만(alignSelf: flex-start), 네 곳 모두
모션 표: 아이콘 scale 0.8 → 1 · 불투명도(옛 스펙에서 온 값 — 레시피 · 미리보기 어디에도 구현된 적이 없다)색 전환(채움 · 테두리 · 아이콘 색) + 누름 축소 — 구현 그대로이고 SEED Checkmark 와 같다
칸(버튼) 위 커서 기본 화살표손가락(cursor-pointer) — 라벨 위와 같게

2026-09-30 — SEED Checkbox 구조로

사용자가 비교 페이지(https://claude.ai/artifact/SERp881jg537tG3nMxeUEB)에서 정했다 — 틀은 SEED(Checkmark · Checkbox · Checkbox Group, 크기 medium 20 · large 24 + 라벨 14 · 16 + 줄 32 · 36) · 선택 색은 neutral 기본 + brand 선택(Radio · Switch 도 같은 규칙) · 모양은 Square + Ghost(할 일 동그라미는 할 일 목록 차례에) · 오류는 묶음 아래 글만.

옛새
sm(16) · md(18, 기본) · lg(20)medium(20, 기본) · large(24)
선택 = primary(브랜드) 채움선택 = bg-neutral-inverted(짙은 회색), tone="brand" 면 bg-brand-solid
선택 안 됨 테두리 border-strong · 바탕 surface-default · 호버 surface-input테두리 stroke-neutral-solid · 바탕 투명 · 호버 = 누름 bg-layer-default-pressed
비활성 50% 흐림bg-disabled · fg-disabled(v106)
오류 aria-invalid 빨간 테두리없음 — 묶음 아래 안내 글
라벨은 쓰는 쪽이(label-md 14/500 · gap-2)Checkbox 가 라벨까지 — t4 14/400(굵게는 weight="bold" 700)
칸 컴포넌트 이름 Checkbox칸은 Checkmark, Checkbox 는 칸 + 라벨

제품은 앱 적용 단계에서 옮긴다 — Desk 웹 공용 Checkbox 6곳 · 앱 PCheckbox 5곳 · HR 웹 shadcn 기본 체크박스 19곳. Desk 의 손으로 그린 흉내(더치페이 참여자 · "나도 포함" · 캘린더 보이기 — 캘린더는 칸 색이 캘린더마다 제 색이라 캘린더 차례에)와 HR 의 일부 선택 없음(권한 "전체" 가 빈칸에 체크)도 그때.

다른 컴포넌트 — Table 의 선택 열(table.md ⓙ · table-examples.mjs)은 칸(Checkmark)을 쓰고 행 전체가 누르는 영역이 된다. Table 차례에 옮긴다. 메뉴의 체크 항목(Dropdown · Context · Menubar 의 CheckboxItem)은 이 컴포넌트가 아니다.

2026-09-29 — 수치를 YAML 로

수치 표를 checkbox.yaml 로 옮겼다(값은 그대로). 이전 md 는 checkbox.history/v-pre-yaml-numbers.md.

이 페이지