ColorPicker | CoUI

ColorPicker

색상을 선택하고 편집하는 색상 선택기 컴포넌트

ColorPicker#

다양한 형식으로 색상을 선택하고 편집할 수 있는 색상 선택기 컴포넌트입니다. Flutter 와 Web 모두 CoColorPicker 로 통일된 API (color / onChanged / showAlpha / showLabel / showHexInput / pickerMode 등) 를 제공하며, 공통 CoreColorPickerTheme / CoreColorPickerTokensData 토큰을 공유합니다. trigger 는 양쪽 모두 [swatch][hex input?] 행으로 동일한 사이즈(40px swatch · 96px hex input · 4px swatch gap) 를 사용합니다.

값 표현은 플랫폼 컨벤션을 따릅니다 — Flutter 는 Color (Color(0xFF3B82F6)), Web 은 #RRGGBB 형식의 hex 문자열. 내부 색상 연산은 양쪽 모두 hex 기반 (core_color_math.dart) 으로 통일되어 있어 정확도와 동작이 동일합니다.

Live Preview#

Web
Flutter
Loading Flutter...
class ColorPickerDefaultExample extends StatefulComponent {
  const ColorPickerDefaultExample({super.key});

  @override
  State<ColorPickerDefaultExample> createState() =>
      _ColorPickerDefaultExampleState();
}

class _ColorPickerDefaultExampleState
    extends State<ColorPickerDefaultExample> {
  String _color = '#3B82F6';

  @override
  Component build(BuildContext context) {
    return CoColorPicker(
      color: _color,
      showHexInput: true,
      onChanged: (value) {
        setState(() => _color = value);
      },
    );
  }
}
class ColorPickerDefaultExample extends StatefulWidget {
  const ColorPickerDefaultExample({super.key});

  @override
  State<ColorPickerDefaultExample> createState() =>
      _ColorPickerDefaultExampleState();
}

class _ColorPickerDefaultExampleState
    extends State<ColorPickerDefaultExample> {
  Color _color = const Color(0xFF3B82F6);

  @override
  Widget build(BuildContext context) {
    return CoColorPicker(
      color: _color,
      showHexInput: true,
      onChanged: (value) {
        setState(() => _color = value);
      },
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 사용자가 브랜드 색상, 테마 색상 등 임의의 색상을 직접 지정해야 하는 경우
  • 디자인 도구, 에디터 등에서 색상 팔레트를 제공해야 하는 경우
  • HEX 값이나 RGB / HSL / HSV 중 특정 색공간으로 값을 입력받아야 하는 경우

대신 다른 컴포넌트를 사용하세요:

  • Select: 고정된 색상 목록에서만 선택해야 하는 경우
  • TextField: 색상 코드를 순수 텍스트로만 입력받는 경우

기본 사용법 (Basic Usage)#

// Color 값 기반 — Flutter 컨벤션
CoColorPicker(
  color: Color(0xFF3B82F6),
  onChanged: (color) => setState(() => _color = color),
  showHexInput: true,
)

// RGB / HSL / HSV 기본 모드 + 알파 슬라이더 + 화면 색 추출
CoColorPicker(
  color: _color,
  onChanged: handleColorChanged,
  showAlpha: true,
  showLabel: true,
  pickerMode: CoreColorPickerMode.rgb,
  enableEyeDropper: true,
)
// hex 문자열 기반 — CSS 컨벤션
CoColorPicker(
  color: '#3B82F6',
  onChanged: (hex) => setState(() => _color = hex),
  showHexInput: true,
)

CoColorPicker(
  color: _color,
  onChanged: handleColorChanged,
  showAlpha: true,
  showLabel: true,
  pickerMode: CoreColorPickerMode.rgb,
  enableEyeDropper: true,
)

Props / Parameters#

공통 Contract 필드 (CoreColorPickerContract):

속성FlutterWeb기본값설명
color Color String 필수 현재 선택된 색상 (Flutter Color, Web #RRGGBB)
onChanged ValueChanged<Color>? CoreValueChanged<String>? null Save 커밋 시 호출되는 콜백 (live-preview 에는 호출되지 않음)
enabled bool? bool? true 인터랙션 가능 여부
showAlpha bool? bool? false 알파 슬라이더 표시
showLabel bool? bool? false swatch 옆 hex 라벨 표시
showHexInput bool? bool? false swatch 옆 hex 텍스트 입력 표시
pickerMode CoreColorPickerMode? CoreColorPickerMode? rgb RGB / HSL / HSV 색공간
enableEyeDropper bool? bool? true EyeDropper 버튼 표시 (Web 은 Chromium 95+ / Flutter 는 web target 에서 동작)
promptMode CoreColorPickerPromptMode? CoreColorPickerPromptMode? popover 트리거 클릭 시 popover/dialog 중 어떤 surface로 열릴지
dialogTitle Widget? Component? null 팝오버 / 다이얼로그 상단 타이틀
placeholder Widget? Component? null 미선택 상태 placeholder
controller Object? Object? null 외부 imperative controller
colorPickerStyle CoreColorPickerStyle<Color, List<BoxShadow>>? CoreColorPickerStyle<String, String>? null 인스턴스 스타일 (Style 시스템 참조)

스타일 시스템 (Style System)#

ColorPicker 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreColorPickerStyle<Clr, Shadow> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 평면 chrome 필드 (popoverAlignment / popoverAnchorAlignment / popoverPadding) 는 모두 제거되었습니다.

시맨틱 vs 스타일#

  • 시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (color, onChanged, enabled, showAlpha, showLabel, showHexInput, pickerMode, enableEyeDropper, promptMode, dialogTitle, placeholder, controller)
  • chrome / dimensional / 슬롯 스타일: CoreColorPickerStyle 한 곳으로 (popoverStyle / dialogStyle / swatchButtonStyle / hexInputStyle / labelStyle / descriptionStyle / errorStyle)
  • 변형(variant) 교체: asChild — dialogTitle / placeholder 슬롯 등에 직접 위젯 주입

Resolve chain#

design system default for color picker
  → CoreColorPickerTheme.style                  // 프로젝트 공통
  → parent component slot override
  → widget.colorPickerStyle                     // 인스턴스별

각 nested 슬롯 스타일 (popoverStyle / dialogStyle / swatchButtonStyle / hexInputStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 예: popoverStyle.triggerStyle 의 chrome 은 → CoreButtonTheme → variant 룩업 → 부모 슬롯 → widget.colorPickerStyle.popoverStyle.triggerStyle 순.

슬롯 매핑 (CoreColorPickerStyle 7 필드)#

필드타입 (Flutter)적용 영역
popoverStyle CorePopoverStyle<Color, List<BoxShadow>>? popover-mode 트리거 + 패널 chrome
dialogStyle CoreDialogStyle<Color>? dialog-mode 모달 chrome
swatchButtonStyle CoreButtonStyle<Color>? preset / history color swatch 버튼
hexInputStyle CoreTextFieldStyle<Color>? 트리거 옆 hex 입력 필드
labelStyle CoreTextStyle<Color>? 트리거 위 라벨 텍스트
descriptionStyle CoreTextStyle<Color>? 도움말 / 설명 텍스트
errorStyleCoreTextStyle<Color>?에러 메시지 텍스트

Migration — 옛 평면 필드 → 새 위치 매핑#

옛 chrome 필드새 위치
popoverAlignment popoverStyle.placement (또는 CoPopoverplacement 직접 지정)
popoverAnchorAlignmentpopoverStyle.placement
popoverPaddingpopoverStyle.panelPadding

사용 예 (Flutter)#

CoColorPicker(
  color: const Color(0xFF3B82F6),
  showHexInput: true,
  onChanged: handleColorChanged,
  colorPickerStyle: CoreColorPickerStyle(
    popoverStyle: CorePopoverStyle(
      panelBorderRadius: 12,
      panelPadding: 16,
      gap: 8,
    ),
    swatchButtonStyle: CoreButtonStyle(
      borderRadius: 4,
    ),
    hexInputStyle: CoreTextFieldStyle(
      borderColor: Color(0xFF3B82F6),
    ),
    labelStyle: CoreTextStyle(fontWeight: 600),
  ),
)

사용 예 (Web)#

CoColorPicker(
  color: '#3B82F6',
  showHexInput: true,
  onChanged: handleColorChanged,
  colorPickerStyle: CoreColorPickerStyle<String, String>(
    popoverStyle: CorePopoverStyle<String, String>(
      panelBorderRadius: 12,
      panelPadding: 16,
    ),
    hexInputStyle: CoreTextFieldStyle<String>(
      borderColor: 'primary',
    ),
  ),
)

변형 (Variants)#

swatch + hex 텍스트 입력#

CoColorPicker(
  color: const Color(0xFF3B82F6),
  showHexInput: true,
  onChanged: handleChanged,
)

EyeDropper#

CoColorPicker(
  color: _color,
  enableEyeDropper: true,
  onChanged: handleChanged,
)

enableEyeDropper: true + 호스트가 window.EyeDropper API 를 지원하면 액션 행에 스포이드 버튼 표시. Chromium 95+ / Flutter Web target 에서 동작. 미지원 환경 (Firefox · Safari · 네이티브 Flutter) 에서는 버튼이 자동 숨김.

최근 색 (Recent Colors)#

CoColorPicker 는 같은 화면 안에서 commit 한 색상을 RecentColorsScope 를 통해 공유합니다. picker 호스팅 화면을 scope 로 wrap 하면 그 안의 모든 picker 가 동일 history 를 보고, Save 액션이 새 색을 push 하면 다른 picker 의 그리드에 즉시 반영됩니다.

// Flutter
RecentColorsScope(
  initialRecentColors: [
    CoreColor.fromHex('#3B82F6'),
    CoreColor.fromHex('#E11D48'),
  ],
  maxRecentColors: 10,
  onRecentColorsChanged: (colors) => persist(colors),
  child: ColorPaletteEditor(...),
)

// Web — 동일 시그니처
RecentColorsScope(
  initialRecentColors: [
    CoreColor.fromHex('#3B82F6'),
    CoreColor.fromHex('#E11D48'),
  ],
  maxRecentColors: 10,
  onRecentColorsChanged: (colors) => persist(colors),
  child: ColorPaletteEditor(...),
)

scope 바깥에서 단독으로 쓰는 picker (예: 스토리북, 단일 다이얼로그) 는 history 그리드 영역이 자동으로 숨겨지고 onChanged Save 콜백만 발사됩니다. App-level prop 이 아닌 picker 호스팅 화면 단위로 wrap 하는 패턴이 정공법 — root 가 아닌 scope 가 필요한 곳에서만 적용됩니다.

그리드 토큰#

그리드 swatch 사이즈와 컬럼 수는 CoreColorPickerTokensDatarecentSwatchSize / recentSwatchGap / recentColumns 에서 결정됩니다 (기본 24px swatch · 4px gap · 10 columns). 양 플랫폼 grid 가 같은 토큰 source 를 참조합니다.

동작 스펙 (Behavior)#

인터랙션#

  • swatch 클릭: 통일된 CoPopover 팝오버 열림 (양 플랫폼 동일 인프라)
  • hex 입력: #RRGGBB 형식 검증 후 _commit 발사
  • mode tab (RGB / HSL / HSV): CoButton(.primary | .ghost) 사용 — hover / focus / disabled 인터랙션 통일 컴포넌트가 처리
  • SV (Saturation / Value) 영역: 마우스 드래그로 saturation / value 동시 조정 (cursor: precise / crosshair)
  • hue 슬라이더: 좌우 드래그로 hue 조정 (cursor: ew-resize)
  • EyeDropper: 지원 환경에서 화면 픽셀 샘플링
  • Outside tap / ESC: Save 안 누른 상태로 닫히면 draft 가 마지막 commit 색으로 자동 롤백

상태 전환#

  • defaultpicking: swatch 클릭 시 (CoPopover open)
  • pickingcommitted: Save 버튼 클릭. onChanged 호출 + RecentColorsScopeaddHistory
  • pickingcancelled: Cancel · outside-tap · ESC. swatch / hex input 이 마지막 committed 색으로 롤백
  • disabled: 트리거 row 비활성, 팝오버 mount 안 됨 (aria-disabled="true")

색 commit 흐름#

swatch click → popover open → live preview drag/type → Save_commit(hex)
                                                                    ↓
                                                              widget.onChanged(color/hex)
                                                                    ↓
                                                              storage.addHistory(CoreColor)

onChanged 는 Save 시점에만 호출되어 의도하지 않은 미커밋 값이 외부 상태로 새지 않습니다.

Theme 오버라이드#

CoreComponentTheme.colorPicker 를 통해 프로젝트 레벨의 기본 스타일 (CoreColorPickerStyle) + behaviour 기본값 (showAlpha / showLabel / showHexInput / pickerMode / enableEyeDropper / promptMode) 을 지정할 수 있습니다:

CoreComponentTheme(
  colorPicker: CoreColorPickerTheme(
    style: CoreColorPickerStyle(
      popoverStyle: CorePopoverStyle(
        panelBorderRadius: 8,
        panelPadding: 16,
      ),
    ),
    showAlpha: true,
    showHexInput: true,
  ),
)

접근성 (Accessibility)#

  • 트리거 wrapper: role="group" / aria-label="Color picker"
  • swatch: <button> — 클릭 / 키보드 / 포커스 인터랙션은 CoPopover 의 trigger handler 가 담당, swatch 자체에는 aria-haspopup="dialog" / aria-expanded 가 popover wrapper 측에서 emit
  • 팝오버 패널: role="dialog" / aria-label="Color picker"
  • mode tab: <button>CoButton 의 표준 포커스 / hover affordance
  • 비활성화: aria-disabled="true" + disabled

크로스 플랫폼 차이점 (Platform Differences)#

API 와 시각 패리티는 통일됨. 플랫폼 간 차이는 구현 매체값 표현 만 다르며 기능 동등.

항목FlutterWeb
클래스명CoColorPickerCoColorPicker
색상 타입ColorString (hex)
Style 제네릭 CoreColorPickerStyle<Color, List<BoxShadow>> CoreColorPickerStyle<String, String>
색공간 선택CoreColorPickerModeCoreColorPickerMode
팝오버 인프라 CoPopover (overlay-driven) CoPopover (CoOverlayHost portal)
EyeDropperWeb target 에서 window.EyeDropper API 호출동일
외부 클릭 / ESC 닫기 CoPopoverdismissOnOutsideTap / dismissOnEscape 동일
RecentColorsScope Data<CoreColorHistoryStorage> InheritedWidget _RecentColorsInherited InheritedComponent
  • TextField: 색상 코드를 직접 텍스트로 입력
  • Slider: 개별 채널 값을 조정
  • Popover: ColorPicker 가 사용하는 팝오버 인프라