ComponentsP3 본문

MultiSelect

다중 선택 콤보박스 — 칩 트리거 + aria-multiselectable 리스트박스. Enter/Space 토글은 목록을 닫지 않고, 포커스는 트리거(검색 시 검색 입력)에 머뭅니다. 비제어(defaultValue) 우선 (DK WizDropDown 사양 승격).

마지막 업데이트 2026-06-11

한눈에#

다중 선택 콤보박스 — 칩 트리거 + aria-multiselectable 리스트박스. 선택값은 트리거 안에 제거 버튼이 달린 칩으로 요약됩니다.

  • 다중 선택
  • 칩 트리거
  • 검색 가능
  • 비제어 우선

기본 — Enter/Space 토글(목록은 닫히지 않음)

사용 시점#

여러 값을 골라 칩으로 요약하면 MultiSelect — 그 외에는 목적에 맞는 컴포넌트를 가리킵니다.

권장 — 이렇게 쓰세요

지양 — 이러지 마세요

쓴다고정 옵션에서 여러 값 동시 선택(담당자·태그·필터 조합)

대신 Select하나만 골라야 할 때

대신 Autocomplete서버 조회·비동기 태그 입력

대신 Chip선택 결과를 input 칩으로 직접 다룰 때

플레이그라운드#

컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.

MultiSelect를 직접 조작해 보세요

속성·테마·토큰을 바꾸고 React·Flutter 코드를 확인하는 풀스크린 빌더로 엽니다.

빌더 열기

플레이그라운드는 넓은 작업 영역이 필요해 웹·태블릿에서 편집할 수 있어요.

변형#

칩 상한 — maxVisibleTags 초과분은 +N 합산
showAll + allowClear — 전체 토글 행과 전체 비우기 ×

showAll이면 목록 맨 위에 “전체” 토글 행이 생기고, 전체가 선택되면 트리거에 칩 나열 대신 전체 1개가 표시됩니다(그 칩의 ×는 전체 해제).

searchable — label 부분일치(대소문자 무시) 검색

열리면 포커스가 검색 입력으로 이동합니다. 검색 중에는 “전체” 행이 숨겨집니다 — 필터된 부분집합에 대한 전체 토글은 의미가 모호하기 때문입니다.

그룹 — 연속된 같은 group은 헤더 아래 묶이고, groupSelectable이면 헤더가 토글 행

그룹 일부만 선택되면 헤더 체크박스가 부분 선택(−)으로 표시됩니다. 비활성 옵션은 전체/그룹 토글에서도 제외됩니다.

크기#

단일 크기입니다 — 트리거는 input.height(44px)를 최소 높이로 칩이 줄바꿈되면 늘어나고, 최소 폭 16rem입니다. 리스트는 최대 16rem 높이에서 스크롤됩니다.

상태#

Disabled · Loading
  • Focus — 트리거 :focus-visibleinput.border-focus 보더 + 18% 링
  • 활성 행color.surface-hover 배경(마우스 호버도 활성을 따라감), 활성+선택 행은 color.surface-selected
  • 선택 행aria-selected + color.primary-text 잉크 + 체크 표시
  • Loading — 목록 대신 Spinner / 빈 결과emptyText(role="status")
  • Disabledinput.bg-disabled 배경 + 포커스·토글 차단

모바일#

DK 모바일 재활용 표준입니다 — mobile을 켜면 팝업 대신 BottomSheet 체크리스트가 열립니다. 데스크톱과 달리 임시 선택입니다: 체크를 바꿔도 즉시 확정되지 않고, footer의 확인을 눌러야 onChange가 한 번 발화합니다. 취소·배경 클릭·Escape·핸들 드래그 다운은 임시 선택을 폐기하며, 시트는 열릴 때마다 커밋된 값으로 새로 시작합니다. searchable·showAll·groupSelectable은 시트 안에서도 동일하게 동작합니다. 검색창은 입력 컨트롤로 자체 테두리를 유지하지만, 체크리스트 표면은 별도 카드 없이 BottomSheet 본문 폭을 채웁니다. 라이브 데모는 데스크톱 뷰포트라 코드로 안내합니다.

<MultiSelect
  mobile
  mobileTitle="에이전트 선택"
  confirmLabel="적용"
  searchable
  showAll
  options={agentOptions}
  onChange={(values) => applyAgents(values)}
/>

Props#

Prop타입기본값설명
optionsreadonly MultiSelectOption[]옵션 배열 (아래 표) — 데이터 prop 방식
defaultValuereadonly string[]초기 선택 value 배열 — 이후 상태는 내부 소유(비제어)
onChange(values: string[]) => void선택 집합 변경 콜백 — 항상 새 배열로 통지
placeholderstring선택선택이 없을 때 트리거에 표시되는 문구
maxVisibleTagsnumber3트리거 칩 노출 상한 — 초과분은 "+N" (기본 3)
showAllbooleanfalse목록 맨 위 "전체" 토글 행 — 전체 선택 시 트리거에 칩 대신 전체 1개 표시
allLabelstring전체전체 행/칩의 라벨
searchablebooleanfalse팝업 상단 검색 입력 — label 부분일치(대소문자 무시)
onSearchChange(query: string) => void검색어 변경 통지 — 서버 필터 연동용
searchPlaceholderstring검색검색 입력 placeholder
searchLabelstring옵션 검색검색 입력 aria-label
groupSelectablebooleanfalse그룹 헤더를 체크박스 행으로 — 전체/부분(indeterminate) 토글
allowClearbooleanfalse트리거 우측 전체 비우기 × 버튼
clearLabelstring모두 지우기비우기 버튼 aria-label
loadingbooleanfalse목록 대신 로딩 표시
emptyTextstring결과 없음옵션/검색 결과 0건 표시 문구
responsive'auto' | 'mobile' | 'desktop'적응형 표시 (ADR-012) — auto(기본): compact 뷰포트에서 borderless/full-width BottomSheet 체크리스트 전환. 검색 input만 control border 유지.
mobilebooleanfalse@deprecated responsive='mobile' 사용. true면 BottomSheet 체크리스트 강제 — 하위호환 alias
mobileTitleReactNode선택시트 제목
confirmLabelstringmobile 확정 버튼 라벨
cancelLabelstringmobile 취소 버튼 라벨
disabledbooleanfalse비활성 — 열기·토글·포커스 차단
refRef<HTMLDivElement><div role="combobox"> 트리거 요소로 병합 전달되는 ref (React 19 ref-as-prop)

MultiSelectOption:

Prop타입기본값설명
valuestring고유 value — 선택 집합의 원소
labelstring표시·검색 대상 라벨
disabledboolean선택 차단 — 전체/그룹 토글에서도 제외 (기본 false)
groupstring같은 group 문자열을 가진 연속 옵션은 그룹 헤더 아래 묶인다

접근성#

트리거가 role="combobox" + aria-haspopup="listbox"(mobile은 "dialog")인 div입니다 — 칩 제거 버튼이 트리거 안에 살기 때문에 중첩 버튼을 피해 div + tabIndex=0으로 구현됩니다. 팝업은 ul[role="listbox"] + aria-multiselectable="true"이고, 포커스는 트리거(searchable이면 검색 입력)에 머물며 aria-activedescendant가 활성 행 id를 가리킵니다.

동작
/ / Enter / Space (닫힘)열기 — 첫 탐색 가능 행 활성
/ (열림)활성 행 이동 — 비활성 옵션 건너뜀
Home / End첫/마지막 행으로 점프 (검색 입력에서는 텍스트 편집에 양보)
Enter / Space활성 행 토글 — 목록 유지 (검색 입력의 Space는 텍스트 입력)
Escape닫기 + 트리거 포커스 복원
Tab닫고 기본 포커스 이동
  • 행 선택 상태는 aria-selected, 부분 선택(전체/그룹)은 data-indeterminate — 시각 체크박스는 aria-hidden
  • 비활성 옵션은 aria-disabled로 보고되고 클릭·키보드 토글이 차단됩니다
  • 외부 클릭으로 닫히며, 옵션 클릭은 mousedown 기본 동작 차단으로 트리거/검색 입력 포커스를 유지합니다
  • 뷰포트 하단 공간이 부족하면 팝업이 위로 플립됩니다 (Select 패턴)

토큰#

component 토큰 없이 Input·Checkbox의 component 토큰과 semantic을 직접 소비합니다(신설 기준 §4 미충족).

속성토큰
트리거input.bg/fg/border/radius · 최소 높이 input.height · placeholder input.placeholder
포커스input.border-focus + color.primary 18% 링
칩 초과(+N)color.surface-muted · color.text-muted · radius.full
팝업color.surface-raised · radius.md · shadow.lg · z.dropdown
행 활성/선택color.surface-hover · 선택 잉크 color.primary-text · 활성+선택 color.surface-selected · 눌림 color.surface-pressed
행 체크박스checkbox.bg/border/radius/check/bg-checked (Checkbox 어휘 공유)
행 높이control.height-sm — 모바일 시트 행은 control.height-md
비활성input.bg-disabled · color.text-placeholder
모션duration.fast + ease.standard/out