Autocomplete
비동기 태그 입력 콤보박스 — 자유 텍스트로 검색하고 외부 주입 suggestions에서 골라 칩으로 쌓습니다. 서버 조회는 소비자가 onQueryChange로 수행하는 비제어 컴포넌트입니다.
마지막 업데이트 2026-06-11
한눈에#
비동기 태그 입력 콤보박스 — 자유 텍스트로 검색하고 외부 주입 suggestions에서 골라 칩으로 쌓습니다. 서버 조회는 소비자가 onQueryChange로 수행합니다.
아직 선택 없음
- 비동기 태그
- 칩 누적
- onQueryChange
- maxSelections
입력하면 후보가 열리고 선택하면 칩으로 쌓입니다
사용 시점#
서버 제안에서 골라 칩으로 쌓으면 Autocomplete — 그 외에는 목적에 맞는 컴포넌트를 가리킵니다.
아직 선택 없음
자유 텍스트 검색 + 서버 제안에서 골라 칩으로 누적(태그 입력)
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
import { Autocomplete } from '@wds/ui-web';
<Autocomplete
aria-label="기술 스택"
suggestions={[
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'svelte', label: 'Svelte' },
{ value: 'angular', label: 'Angular' },
]}
/>변형#
아직 선택 없음
defaultValue로 초기 칩을 채울 수 있고, suggestions에 없는 값은 value
문자열이 그대로 칩 라벨이 됩니다. 이미 선택된 항목은 목록에서 제외됩니다.
크기#
단일 크기입니다 — 트리거 최소 높이는 input.height(44px)로 Input과 같은 폼
컨트롤 줄맞춤이고, 칩이 늘어나면 줄바꿈하며 세로로 자랍니다. 최소 폭은
16rem(MultiSelect와 동일), 팝업 리스트는 최대 16rem 높이에서 스크롤됩니다.
상태#
- Focus — 포커스 링은 wrap
:focus-within에input.border-focus보더 + 18% 링 (Input과 동일) - Loading —
loading이면 목록 대신 스피너 행(role="status", '로딩 중') - Empty — 남은 후보가 없으면
emptyText('결과 없음') 행 - Max 도달 — 입력
disabled+ 안내 문구(role="status",aria-describedby연결) - Disabled —
input.bg-disabled배경, 칩 제거 버튼도 함께 숨김
비제어 컴포넌트입니다 — defaultValue로 시작하고 칩 추가/제거 시
onChange(values)로 알립니다.
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
suggestions | ReadonlyArray<AutocompleteSuggestion> | — | { value, label, description? } 후보 배열 — 서버 조회 결과를 외부에서 주입 |
defaultValue | ReadonlyArray<string> | — | 초기 선택 value 배열 — 이후 상태는 내부 소유(비제어) |
onChange | (values: string[]) => void | — | 칩 추가/제거 시 선택 value 배열 콜백 |
onQueryChange | (query: string) => void | — | 입력 쿼리 변경 콜백 — 서버 조회 트리거 지점 (선택 확정 시 빈 문자열로 재호출) |
loading | boolean | false | 목록 대신 스피너 표시 |
maxSelections | number | — | 선택 상한 — 도달 시 입력 비활성 + 안내 표시 |
maxSelectionsText | string | '최대 N개까지 선택할 수 있습니다' | 상한 도달 안내 문구 오버라이드 |
emptyText | string | '결과 없음' | 남은 후보가 없을 때 표시 문구 |
placeholder | string | '입력하여 검색' | 칩이 없을 때 입력에 표시되는 문구 |
…rest | Omit<InputHTMLAttributes, 'onChange' | 'value' | 'defaultValue'> | — | aria-label · disabled 등은 입력 요소로 전달 |
접근성#
입력이 role="combobox" + aria-autocomplete="list" +
aria-haspopup="listbox", 팝업이 role="listbox"입니다. 포커스는 항상 입력에
머물고 aria-activedescendant가 활성 옵션 id를 가리킵니다. 칩 제거 버튼은
aria-label="{label} 제거"로 이름이 붙고 44px 히트 영역을 가집니다.
| 키 | 동작 |
|---|---|
| 문자 입력 | 목록 열기 + onQueryChange 발화 |
↓ (닫힘) | 목록 열기 — 첫 옵션 활성 |
↓ / ↑ (열림) | 활성 옵션 이동 — 양 끝에서 클램프 |
Enter | 활성 옵션 선택 → 칩 추가 + 쿼리 비움 + 닫기 |
Escape | 선택 없이 닫기 |
Backspace (빈 입력) | 마지막 칩 제거 |
외부 클릭으로도 닫히며, 상한 도달 안내는 role="status" +
aria-describedby로 입력과 연결됩니다.
토큰#
component 토큰 없이 Input의 component 토큰(input.*)과 semantic을 직접
소비합니다(신설 기준 §4 미충족).
| 속성 | 토큰 |
|---|---|
| 트리거 | input.bg/fg/border/height/radius · placeholder input.placeholder |
| 포커스 | input.border-focus + color.primary 18% 링 (:focus-within) |
| 칩 | Tag(neutral sm) — color.surface-muted · color.text-muted · radius.full |
| 팝업 | color.surface-raised · radius.md · shadow.lg · z.dropdown |
| 옵션 활성 | color.surface-hover · 눌림 color.surface-pressed · 설명 잉크 color.text-subtle |
| 옵션 높이 | control.height-sm |
| 안내/빈 상태 | color.text-subtle · color.text-muted |
| 비활성 | input.bg-disabled |
| 모션 | duration.fast + ease.out/standard |