Chip | CoUI

Chip

태그, 필터, 선택 상태를 표시하는 칩 컴포넌트

Chip#

태그, 필터 옵션, 선택 상태 등을 표시하는 칩 컴포넌트입니다. 삭제 버튼, 아바타, 선택 상태를 지원합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 카테고리, 태그, 키워드를 시각적으로 표현할 때
  • 필터 옵션을 선택/해제할 수 있는 토글 가능한 레이블이 필요할 때
  • 선택된 항목 목록을 삭제 가능한 태그로 표시할 때 (예: 이메일 수신자 목록)
  • 다중 선택 UI에서 선택된 항목을 표시할 때

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

  • Badge: 상태나 숫자를 표시하는 작은 표시자가 필요할 때 (상호작용 없음)
  • Button: 클릭 시 명확한 단일 액션이 실행되어야 할 때
  • ChipInput: 텍스트를 직접 입력하여 칩을 생성하는 입력 필드가 필요할 때

기본 사용법 (Basic Usage)#

// 기본 칩
CoChip(
  label: 'Flutter',
)

// 삭제 가능한 칩
CoChip(
  label: 'Dart',
  onRemove: handleDeleteDart,
)

// leading 위젯 포함 칩
CoChip(
  label: '홍길동',
  leading: CoAvatar(
    imageUrl: 'https://example.com/avatar.jpg',
    size: CoreComponentSize.xs,
  ),
  onRemove: handleDeleteUser,
)

// 선택 가능한 칩
CoChip(
  label: '디자인',
  chipColor: CoreChipColor.primary,
  isSelected: isDesignSelected,
  onTap: handleSelectDesign,
)
// 기본 칩
CoChip(label: 'Flutter')

// 삭제 가능한 칩
CoChip(
  label: 'Dart',
  onRemove: handleDeleteDart,
)

// leading 포함 칩
CoChip(
  label: '홍길동',
  leading: CoAvatar(
    initials: '홍',
    size: CoreComponentSize.xs,
  ),
  onRemove: handleDeleteUser,
)

// 선택 가능한 칩
CoChip(
  label: '디자인',
  chipColor: CoreChipColor.primary,
  isSelected: isDesignSelected,
  onTap: handleSelectDesign,
)

Props / Parameters#

속성타입기본값설명
label String? null 칩에 표시할 텍스트
child Widget? null 커스텀 내용 (label 대체)
chipColor CoreChipColor neutral 칩 색상 변형
size CoreComponentSize md 칩 크기
isSelected bool false 선택 상태 여부
isDisabled bool false 비활성화 여부
onTap VoidCallback? null 탭 핸들러
onRemove VoidCallback? null 삭제 버튼 클릭 핸들러. null이면 삭제 버튼 미표시
leading Widget? null 레이블 앞 위젯
trailing Widget? null 레이블 뒤 위젯
seedColor Color? / CoreColor? null 커스텀 시드 색상
maxLength int? null 레이블 최대 글자 수
chipStyle CoreChipStyle<Color>? / CoreChipStyle<CoreColor>? null 인스턴스 스타일 (Style 시스템 참조)

스타일 시스템 (Style System)#

Chip 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreChipStyle<Clr> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 시맨틱 enum (chipColor / size / isSelected / isDisabled) 과 behaviour 필드는 위젯 파라미터로 직접 전달합니다.

시맨틱 vs 스타일#

  • 시맨틱 enum / behaviour: 위젯 파라미터로 직접 (chipColor, size, isSelected, isDisabled, onTap, onRemove, label, leading, trailing, seedColor, maxLength)
  • chrome / dimensional / 슬롯 스타일: CoreChipStyle 한 곳으로 (backgroundColor / foregroundColor / borderColor / borderWidth / borderRadius / paddingH / paddingV / gap / labelStyle / leadingIconStyle / trailingIconStyle / closeButtonStyle)
  • close 버튼 변형 교체: asChild — 직접 CoButton(variant: ghost, ...) 위젯 주입은 권장 X. closeButtonStyle 로 chrome 만 조정하면 됩니다 (Epic #1302 원칙 8). 내부적으로 CoButton(variant: ghost) 가 사용됩니다.

Resolve chain#

design system default for chip
  → CoreChipTheme.style                    // 프로젝트 공통
  → parent component slot override
  → widget.chipStyle                       // 인스턴스별

각 nested 슬롯 스타일 (labelStyle / leadingIconStyle / trailingIconStyle / closeButtonStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.

슬롯 매핑 (CoreChipStyle 12 필드)#

필드타입 (Flutter)적용 영역
backgroundColor Color? 칩 배경 (variant 기본값 위에 오버라이드)
foregroundColorColor?라벨 전경 색
borderColorColor?보더 색
borderWidthdouble?보더 두께 (px)
borderRadius double? 모서리 반지름 (px). null → 칩 토큰 기본값
paddingHdouble?좌/우 패딩 (px)
paddingVdouble?위/아래 패딩 (px)
gapdouble?leading / label / trailing 간격 (px)
labelStyleCoreTextStyle<Color>?라벨 텍스트 스타일
leadingIconStyle CoreIconStyle<Color>? leading 아이콘 슬롯 스타일
trailingIconStyle CoreIconStyle<Color>? trailing 아이콘 슬롯 스타일 (close 와 다름)
closeButtonStyle CoreButtonStyle<Color>? onRemove close 버튼 chrome (CoButton(variant: ghost) 으로 렌더)

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

CoreChipThemepaddingH / paddingV 필드는 legacy 호환을 위해 보존되지만, 새 코드에서는 chipStyle.paddingH / paddingV 를 사용하세요.

옛 위치 (legacy)새 위치
CoreChipTheme.paddingH chipStyle.paddingH (인스턴스) 또는 CoreChipTheme.style.paddingH (프로젝트)
CoreChipTheme.paddingV chipStyle.paddingV 또는 CoreChipTheme.style.paddingV
인스턴스 라벨 텍스트 스타일 (불가능)chipStyle.labelStyle
인스턴스 아이콘 스타일 (불가능) chipStyle.leadingIconStyle / trailingIconStyle
인스턴스 close 버튼 chrome (불가능)chipStyle.closeButtonStyle

사용 예 (Flutter)#

CoChip(
  label: 'Selected',
  chipColor: CoreChipColor.primary,
  isSelected: true,
  onRemove: handleRemove,
  chipStyle: CoreChipStyle(
    paddingH: 12,
    paddingV: 6,
    borderRadius: 16,
    labelStyle: CoreTextStyle(fontWeight: 600),
    closeButtonStyle: CoreButtonStyle(
      paddingH: 4,
      paddingV: 4,
    ),
  ),
)

사용 예 (Web)#

CoChip(
  label: 'Selected',
  chipColor: CoreChipColor.primary,
  isSelected: true,
  onRemove: handleRemove,
  chipStyle: CoreChipStyle<CoreColor>(
    paddingH: 12,
    paddingV: 6,
    borderRadius: 16,
  ),
)

변형 (Variants)#

Neutral (기본)#

중립 색상의 기본 칩입니다.

CoChip(label: 'Neutral', chipColor: CoreChipColor.neutral)

Primary#

강조 색상 칩입니다.

CoChip(label: 'Primary', chipColor: CoreChipColor.primary)

Destructive#

위험/삭제 표시용 칩입니다.

CoChip(label: 'Destructive', chipColor: CoreChipColor.destructive)

칩 그룹#

여러 칩을 필터 그룹으로 묶어 사용할 수 있습니다.

Wrap(
  spacing: 8,
  children: [
    CoChip(label: 'Flutter', onRemove: () => handleDelete('flutter')),
    CoChip(label: 'Dart', onRemove: () => handleDelete('dart')),
    CoChip(label: 'UI', onRemove: () => handleDelete('ui')),
  ],
)

동작 스펙 (Behavior)#

인터랙션#

  • 클릭/탭 (선택형): onSelected 콜백으로 selected 상태 토글
  • 삭제 버튼 클릭: onDelete 콜백 실행. 칩 제거는 부모에서 처리
  • 호버: 배경색 미세 변화로 상호작용 가능 상태 표시
  • 포커스: 포커스 링 표시

상태 전환#

  • defaulthoverpresseddefault
  • selected 상태에서 클릭: onSelected(false) 호출
  • disabled 상태: 클릭 불가, 반투명 처리

애니메이션#

  • 선택 상태 전환: 배경색 150ms ease
  • 삭제 시 (부모 Wrap에서): AnimatedSize 또는 AnimatedOpacity 사용 권장

사용 가이드라인 (Usage Guidelines)#

✅ Do#

필터 그룹에서 선택형 칩 사용

Wrap(
  spacing: 8,
  children: categories.map((cat) => CoChip(
    label: cat.name,
    chipColor: CoreChipColor.primary,
    isSelected: selectedCategories.contains(cat.id),
    onTap: () => handleToggleCategory(cat.id),
  )).toList(),
)

선택형 칩은 필터 옵션을 토글하는 데 직관적인 UI를 제공합니다.


❌ Don't#

칩에 긴 텍스트 사용 금지

// ❌ 너무 긴 레이블
CoChip(
  label: '사용자가 최근에 방문한 카테고리 항목', // 너무 길다
)

칩은 짧고 간결한 레이블에 최적화되어 있습니다. 긴 텍스트는 레이아웃을 깨뜨리고 가독성을 저해합니다. 2~3단어 이내로 제한하세요.

✅ Do#

삭제 가능한 칩 목록에 애니메이션 적용

AnimatedList(
  initialItemCount: tags.length,
  itemBuilder: (context, index, animation) => SizeTransition(
    sizeFactor: animation,
    child: CoChip(
      label: tags[index],
      onRemove: () => handleRemoveTag(index),
    ),
  ),
)

칩 삭제 시 자연스러운 애니메이션은 사용자에게 변화를 시각적으로 전달합니다.


❌ Don't#

비활성화된 칩에 삭제 버튼 표시 금지

// ❌ disabled이지만 onDelete가 있음
CoChip(
  label: '읽기 전용 태그',
  isDisabled: true,
  onRemove: handleDelete, // disabled 상태에서는 의미 없음
)

비활성화된 칩에 삭제 버튼이 있으면 사용자가 혼란스러워합니다. disabled: true일 때는 onDelete를 null로 설정하세요.

✅ Do#

Chip의 label은 간결하게 작성하세요.

// ✅ 짧고 명확한 레이블
CoChip(label: '모바일', onTap: handleMobileSelected)
CoChip(label: 'UX', onTap: handleUxSelected)
CoChip(label: 'Flutter', onTap: handleFlutterSelected)

Chip은 키워드나 태그처럼 짧은 텍스트를 표시하는 용도입니다. 간결한 레이블이 스캔과 선택을 쉽게 만듭니다.


❌ Don't#

Chip에 긴 문장을 넣지 마세요.

// ❌ 너무 긴 Chip 레이블
CoChip(
  label: '사용자가 직접 등록한 관심 카테고리',  // 너무 길다
  onTap: handleCategorySelected,
)

긴 텍스트는 Chip의 시각적 일관성을 깨고 레이아웃을 혼란스럽게 만듭니다. Badge나 Tag 컴포넌트를 고려하세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Enter / Space선택형 칩 토글
Delete / Backspace삭제 가능한 칩 제거
Tab다음 칩 또는 삭제 버튼으로 이동

스크린 리더#

  • Flutter: Semanticsrole="checkbox" (선택형) 또는 role="button" (삭제형) 전달
  • Web: 선택형 칩에 aria-selected, 삭제 버튼에 aria-label="[레이블] 제거" 적용

터치 타겟#

  • 최소 터치 타겟: 32px (칩 높이) + 패딩 포함 48dp 보장
  • 삭제 버튼 별도 터치 영역: 최소 24x24dp

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

항목FlutterWeb
클래스명CoChipCoChip
탭 핸들러onTaponTap
삭제 핸들러onRemoveonRemove
색상 변형 chipColor: CoreChipColor chipColor: CoreChipColor
그룹 레이아웃Wrap 위젯flexbox 자동 처리
  • ChipInput: 텍스트 입력으로 칩을 동적으로 추가하는 입력 필드
  • Badge: 상호작용 없이 상태나 숫자만 표시할 때 사용
  • Button: 클릭 시 명확한 단일 액션이 실행되어야 할 때

조합 예제#

// Chip + ChipInput 조합으로 태그 입력 UI 구현
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Wrap(
      spacing: 8,
      runSpacing: 4,
      children: selectedTags.map((tag) => CoChip(
        label: tag,
        onRemove: () => handleRemoveTag(tag),
      )).toList(),
    ),
    SizedBox(height: 8),
    CoChipInput(
      placeholder: '태그 추가...',
      onAdd: handleAddTag,
    ),
  ],
)