ColorPicker
스와치/field 트리거 + 스펙트럼·그리드·슬라이더 모드 색상 선택기 — compact에서는 BottomSheet, expanded에서는 팝업으로 적응합니다. 진실원천은 HSV(+alpha)라 s=0에서도 hue가 보존되고 onChange 출력은 항상 HEX입니다.
마지막 업데이트 2026-07-01
한눈에#
스와치/field 트리거 + 스펙트럼·그리드·슬라이더 모드 색상 선택기입니다. compact에서는 BottomSheet, expanded에서는 팝업으로 적응합니다. 진실원천은 HSV(+alpha)라 채도 0에서도 hue가 보존되고, onChange는 항상 HEX입니다.
- 스펙트럼·그리드·슬라이더
- HSV 원천
- HEX 출력
- 최근 색
스와치를 눌러 팝업을 열고 상단 탭으로 모드를 전환하세요
트리거는 현재 색 스와치이고, 팝업 상단의 소형 탭으로 스펙트럼(SV+Hue) ·
그리드(12×10 생성 팔레트) · 슬라이더(R/G/B 채널) 모드를 전환합니다.
진실원천은 HSV라 채도 0에서도 색조(hue)가 보존되고, onChange는 항상 대문자
HEX(alpha<1이면 #RRGGBBAA)로 발화합니다. 입력 행에는 표시 포맷 토글
(HEX/RGB/HSL) + 색 코드 입력 + 복사 버튼이 있고, 브라우저가 지원하면
스포이드(EyeDropper) 버튼이 함께 노출됩니다. 색을 바꾸고 팝업을 닫으면
‘최근 색상’ 그룹에 저장됩니다(localStorage, 최신순 최대 10개).
사용 시점#
- 이럴 때: 사용자가 임의의 색 값(HEX/RGB/HSL)을 직접 고르거나 입력해야 할 때 — 테마 색 지정·브랜드 색 선택·디자인 도구.
- 대신 고정 팔레트: 미리 정해진 소수의 색 중 하나만 고르면 ColorPicker 대신 SegmentedButton·스와치 그룹이 더 빠릅니다(임의 색 입력이 불필요).
- 출력은 항상 HEX:
onChange는 대문자 HEX로 발화합니다(진실원천 HSV) — 소비자는 HEX를 받아 저장하면 됩니다.
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
ColorPicker를 직접 조작해 보세요
속성·테마·토큰을 바꾸고 React·Flutter 코드를 확인하는 풀스크린 빌더로 엽니다.
플레이그라운드는 넓은 작업 영역이 필요해 웹·태블릿에서 편집할 수 있어요.
변형#
modes로 패널 모드를 제한할 수 있고 1종이면 탭이 사라집니다. trigger='field'는
스와치와 현재 HEX 텍스트를 함께 보여주는 Input 어휘 높이 버튼입니다.
format은 초기 표시 포맷일 뿐이고 패널 토글로 언제든 순환(HEX→RGB→HSL)하며,
입력 파싱은 세 포맷 모두 수용합니다. defaultValue가 alpha 명시 형식이면
showAlpha 없이 Alpha 슬라이더가 자동 노출되고, 반투명 스와치/알파 트랙 아래에는
체커보드(color.border 격자)가 깔립니다. presets를 주면 팝업 하단에 스와치
그리드가 생기고, 현재 색과 일치하는 항목은 aria-pressed로 표시됩니다.
floating(기본 false)은 패널을 document.body로 포털(position: fixed)해 오버플로
스크롤 컨테이너(표·사이드바 등) 안에서 잘리지 않게 합니다 — 트리거 아래(공간 부족 시 위)에
뜨고 스크롤·리사이즈에 추종하며, 외부 클릭 감지는 포털된 패널 내부도 “안쪽”으로 인정합니다.
인-플로우가 기본이라 평상시엔 끌 필요가 없고, 클리핑되는 컨테이너 안에서만 켜세요(기본값이라
기존 사용처는 영향 없음).
모바일#
responsive='auto'(기본)에서는 compact 뷰포트가 되면 데스크톱 16rem 팝업 대신
BottomSheet로 색상 편집 표면을 렌더합니다.
시트 내부는 별도 카드·테두리·그림자 없이 본문 폭을 채우고, HEX 입력·포맷 토글처럼
컨트롤 자체가 입력 표면인 요소만 자체 border를 유지합니다. 강제 전환이 필요하면
responsive='mobile', 데스크톱 팝업 고정이 필요하면 responsive='desktop'을 사용합니다.
시트 제목은 mobileTitle, 없으면 label을 씁니다.
크기#
단일 크기입니다 — 스와치 트리거는 시각 32px(space.8)에 히트 영역 44px,
field 트리거는 input.height(44px)입니다. expanded 팝업 폭은 16rem, compact 시트는
BottomSheet 본문 폭을 사용합니다. SV 스펙트럼은 높이 10rem, 슬라이더 트랙 높이는
space.3(44px 히트)입니다.
상태#
- 트리거 Default —
color.surface+color.border-strong보더(field는input.bg/border), 스와치는 인라인 동적 색 + 체커보드 - Hover / 열림 —
color.primary보더 - Focus —
:focus-visible에color.primary2px 아웃라인 - 모드 탭 선택 —
color.surface-raised+shadow.sm(aria-selected) - 그리드 셀 선택 — 셀 인지 명도 기반 대비 링(
aria-pressed) — 밝은 색엔 어두운 링, 어두운 색엔 밝은 링 - 스펙트럼/슬라이더 썸 Focus —
color.primary18% 링 - 입력 오류 — 형식 위반 시
aria-invalid+input.border-invalid보더(값은 반영하지 않음) - 프리셋/최근 선택 — 현재 색과 일치하면
aria-pressed+color.primary아웃라인
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
defaultValue | string | #1A75EB | 초기 색 — #RRGGBB(AA)·rgb()/rgba()·hsl()/hsla() 수용, 이후 상태는 내부 소유(비제어) |
onChange | (hex: string) => void | — | 색 변경 콜백 — 항상 대문자 HEX(alpha<1이면 #RRGGBBAA) |
showAlpha | boolean | false | Alpha 슬라이더 노출 — defaultValue가 alpha 명시 형식이면 자동 on |
presets | readonly string[] | — | 프리셋 스와치 그리드 — 각 항목 HEX |
modes | readonly ColorPickerMode[] | ['spectrum', 'grid', 'sliders'] | 패널 모드 구성 — 2개 이상이면 상단 소형 탭 (기본 3종 전부) |
trigger | 'swatch' | 'field' | swatch | 트리거 변형 (기본 'swatch') |
format | 'hex' | 'rgb' | 'hsl' | hex | 초기 표시 포맷 — 패널 토글로 전환, onChange 출력은 항상 HEX |
recentStorageKey | string | wds-color-picker-recent | 최근 색 localStorage 키 override |
recentLabel | string | 최근 색상 | 최근 색 그룹 라벨 override |
label | string | 색상 선택 | 트리거 aria-label 접두 override — '{label}: {hex}' |
className | string | — | 루트 div 클래스 |
floating | boolean | false | 패널을 document.body로 포털(position:fixed) — overflow 스크롤 컨테이너(표·사이드바) 안에서 잘림 방지. 트리거 아래(공간 부족 시 위)에 뜨고 스크롤·리사이즈에 추종. 기본 false(인-플로우, 기존 동작 불변). |
responsive | 'auto' | 'mobile' | 'desktop' | — | 적응형 표시 — auto(기본): compact 뷰포트에서 BottomSheet 색상 편집 표면으로 전환. mobile/desktop은 강제 override |
mobile | boolean | false | @deprecated responsive='mobile' 사용. true면 BottomSheet 색상 편집 표면 강제 |
mobileTitle | string | — | 모바일 시트 제목 — 기본: label |
ref | Ref<HTMLButtonElement> | — | 트리거 <button>으로 전달되는 ref (React 19 ref-as-prop) |
접근성#
트리거 버튼은 aria-label="{label}: {hex}" + aria-expanded로 현재 색을
낭독합니다. 모드 탭은 APG Tabs(roving tabindex + 자동 활성화), 그리드는
role="group" 안에서 각 셀이 aria-label=HEX 버튼이며 화살표로 이동합니다.
SV 스펙트럼 썸은 role="slider" + aria-valuetext(채도/명도%), Hue·Alpha·R/G/B는
네이티브 <input type="range">입니다.
| 키 | 동작 |
|---|---|
트리거 Enter / Space | 팝업/BottomSheet 토글 (네이티브 button) |
모드 탭 ← / → / Home / End | 탭 포커스 이동 + 자동 활성화 (APG) |
스펙트럼 썸 ← / → | 채도(s) 1%p 감소 / 증가 |
스펙트럼 썸 ↑ / ↓ | 명도(v) 1%p 증가 / 감소 |
그리드 셀 ← → ↑ ↓ / Home / End | roving 셀 이동 (행 폭 12) |
| Hue/Alpha/R/G/B 슬라이더 | 네이티브 range 키보드(화살표/Home/End) |
Escape | 팝업 닫기 (논모달 — 포커스는 건드리지 않음) |
- expanded 팝업은 외부 클릭·Escape로 닫히는 논모달입니다(Popover 계약) — 포커스 트랩 없음
- compact BottomSheet는 시트 표준 계약(스크림·포커스 트랩·Escape/배경 닫기)을 따릅니다
- 색 코드 입력은 형식 위반(
aria-invalid)이면 값을 반영하지 않습니다(경계 검증 — HEX/RGB/HSL 모두 파싱) - 동적 색(스와치·그라디언트)은 인라인 style이 소유하고 CSS 파일은 토큰만 씁니다(DD-4)
- 복사 버튼은 CopyButton 합성 — 결과를
aria-live로 공지합니다
토큰#
component 토큰 없이 semantic(+input 어휘)을 직접 소비합니다(신설 기준 §4 미충족 — 동적 색은 토큰이 아닌 데이터 값).
| 속성 | 토큰 |
|---|---|
| 트리거 | color.surface · color.border-strong — hover/열림 color.primary · radius.md · 크기 space.8 |
| field 트리거 | input.bg/fg/border · input.height · input.radius · font.mono |
| 팝업 | color.surface-raised · color.border · radius.md · shadow.lg · z.dropdown |
| 모드 탭 | color.surface-muted 트랙 — 선택 color.surface-raised + shadow.sm · font-size.caption |
| 그리드 셀 | 선택 링 black/white(명도 대비) — Focus color.focus-ring 아웃라인 |
| 체커보드 | color.border 반복 그라디언트 (space.2 격자) |
| 스펙트럼 썸 | white · shadow.sm — Focus color.primary 18% 링 |
| 슬라이더 썸 | white · shadow.md — Focus color.primary 18% 링 |
| 색 코드 입력/포맷 토글 | input.bg/fg/border · font.mono — 오류 input.border-invalid |
| 스포이드 | color.text-muted · color.border — hover color.primary |
| 프리셋/최근 | color.border — 선택/Focus color.focus-ring 아웃라인 · 캡션 color.text-muted |
| 모션 | duration.fast + ease.out/standard |