AutoComplete | CoUI

AutoComplete

입력에 따라 제안 목록을 표시하는 자동 완성 입력 컴포넌트

AutoComplete#

사용자 입력에 따라 일치하는 제안 목록을 실시간으로 표시하는 자동 완성 입력 컴포넌트입니다.

Live Preview#

Web
Flutter
Loading Flutter...
AutoComplete(
  suggestions: ['Apple', 'Banana', 'Cherry'],
  placeholder: 'Search fruits...',
  onSelected: handleSelected,
)
AutoComplete(
  suggestions: ['Apple', 'Banana', 'Cherry'],
  placeholder: 'Search fruits...',
  onSelected: handleSelected,
)

사용 시기 (When to Use)#

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

  • 사전에 알려진 값 목록에서 선택해야 하는 경우 (국가, 도시, 사용자 이름 등)
  • 검색 입력에서 실시간 제안을 제공해야 하는 경우
  • API로부터 동적으로 제안을 가져와야 하는 경우
  • 자유 입력도 허용하면서 제안도 함께 제공해야 하는 경우

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

  • Select: 선택지가 고정되어 있고 자유 입력이 필요 없는 경우
  • ChipInput: 여러 항목을 태그 형태로 선택해야 하는 경우
  • Input: 제안 없이 순수한 텍스트 입력만 필요한 경우

기본 사용법 (Basic Usage)#

// 기본 자동 완성
AutoComplete(
  suggestions: ['Apple', 'Banana', 'Cherry', 'Grape'],
  onSelected: handleFruitSelected,
  labelText: '과일 검색',
)

// 비동기 검색
AutoComplete(
  asyncSearch: (query) async {
    final results = await searchApi.search(query);
    return results.map((r) => r.name).toList();
  },
  onSelected: handleResultSelected,
  debounceMs: 300,
  labelText: '검색어 입력',
)

// 커스텀 필터
AutoComplete(
  suggestions: cityList,
  filter: (item, query) => item.toLowerCase().startsWith(query.toLowerCase()),
  onSelected: handleCitySelected,
  labelText: '도시 선택',
)
// 기본 자동 완성
AutoComplete(
  suggestions: ['Apple', 'Banana', 'Cherry', 'Grape'],
  onSelected: handleFruitSelected,
  placeholder: '과일 검색',
)

// 비동기 작동 — onChanged로 입력마다 외부 검색 트리거
AutoComplete(
  suggestions: searchResults, // 외부에서 업데이트되는 목록
  onChanged: handleQueryChanged, // 입력 시 API 호출 후 suggestions 갱신
  onSelected: handleResultSelected,
  placeholder: '검색어 입력',
)

// 커스텀 필터링 — 시작 문자 일치 필터 예시
AutoComplete(
  suggestions: cityList,
  onSelected: handleCitySelected,
  placeholder: '도시 선택',
  // Web은 브라우저 datalist 기반 — 커스텀 필터는 suggestions를 외부에서 가공
)

Props / Parameters#

속성타입기본값설명
suggestions List<String>? null 정적 제안 목록
onSelected ValueChanged<String>? null 항목 선택 콜백
labelText String? null 입력 필드 라벨
filter bool Function(String item, String query)? null 커스텀 필터 함수
asyncSearch Future<List<String>> Function(String query)? null 비동기 검색 함수
debounceMs int 300 비동기 검색 딜레이 (밀리초)
maxSuggestions int 5 최대 표시 제안 수
onChanged ValueChanged<String>? null 입력 값 변경 콜백

변형 (Variants)#

정적 목록#

AutoComplete(
  suggestions: countries,
  onSelected: handleCountrySelected,
  labelText: '국가 선택',
  maxSuggestions: 8,
)

비동기 검색#

AutoComplete(
  asyncSearch: (query) => userRepository.searchUsers(query),
  onSelected: handleUserSelected,
  debounceMs: 500,
  labelText: '사용자 검색',
)

동작 스펙 (Behavior)#

인터랙션#

  • 입력: 사용자가 타이핑하면 필터링된 제안 목록이 드롭다운으로 표시됨
  • 선택: 제안 항목 클릭 또는 키보드 방향키 + Enter로 선택
  • 닫기: 외부 클릭, Escape 키, 또는 항목 선택 시 드롭다운 닫힘
  • 호버: 제안 항목에 마우스 오버 시 시각적 하이라이트 표시

상태 전환#

  • idlefocused (입력 필드 포커스 시)
  • focused + 입력 → suggesting (제안 목록 표시)
  • suggestingselected (항목 선택 시)
  • suggestingidle (Escape 또는 외부 클릭 시)
  • disabled 상태에서는 모든 인터랙션 무시

디바운스#

  • 비동기 검색(asyncSearch) 사용 시 기본 300ms 디바운스 적용
  • 연속 입력 중 불필요한 API 호출 방지
  • debounceMs: 0으로 즉시 검색 가능

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

✅ Do#

비동기 검색에 적절한 디바운스 설정

CouiAutoComplete(
  asyncSearch: (query) => productApi.search(query),
  onSelected: handleProductSelected,
  debounceMs: 300, // API 호출 빈도 조절
  labelText: '상품 검색',
)

불필요한 API 요청을 방지하고 서버 부하를 줄인다.


❌ Don't#

디바운스 없이 매 입력마다 API 호출

// ❌ debounceMs: 0으로 설정하면 모든 키 입력마다 API 호출
CouiAutoComplete(
  asyncSearch: (query) => productApi.search(query),
  onSelected: handleProductSelected,
  debounceMs: 0,
)

과도한 API 호출로 서버 부하가 발생하고 UX가 저하된다.

✅ Do#

최대 제안 수를 적절히 제한

CouiAutoComplete(
  suggestions: allCountries, // 200여 개 국가
  onSelected: handleCountrySelected,
  maxSuggestions: 8, // 화면에 적절한 수만 표시
  labelText: '국가 선택',
)

너무 많은 제안은 스크롤이 필요해 사용성이 떨어진다.


❌ Don't#

검색 결과 없음 피드백 생략

// ❌ 빈 목록만 보여주면 사용자가 혼란스러움
CouiAutoComplete(
  suggestions: [], // 아무것도 없을 때 빈 드롭다운 표시
  onSelected: handleSelected,
)

검색 결과가 없을 때 명확한 안내 메시지를 제공해야 사용자 경험이 향상된다.

✅ Do#

선택지가 고정된 경우 Select를 대신 사용하세요.

// ✅ 고정된 5개 옵션이라면 Select가 더 적합
CouiSelect(
  options: ['서울', '부산', '대전', '대구', '광주'],
  onChanged: handleCityChanged,
  label: '도시 선택',
)

AutoComplete는 목록이 길거나 동적으로 변하는 경우에 적합합니다. 고정 옵션이 소수라면 Select가 더 명확합니다.


❌ Don't#

빈 쿼리에도 전체 목록을 노출하지 마세요.

// ❌ 포커스만 해도 전체 200개 국가 표시
CouiAutoComplete(
  suggestions: allCountries, // 200개
  onSelected: handleCountrySelected,
  showSuggestionsOnEmpty: true, // 빈 입력에도 전체 표시
)

빈 쿼리에 전체 목록을 보여주면 사용자가 압도될 수 있습니다. 최소 1~2자 입력 후 제안을 표시하세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
/ 제안 목록 항목 탐색
Enter선택된 제안 항목 확정
Escape제안 목록 닫기
Tab다음 포커스 가능 요소로 이동 (목록 닫힘)

스크린 리더#

  • Flutter: Semantics 위젯으로 combobox role과 제안 목록 상태(expanded/collapsed) 전달
  • Web: role="combobox", aria-autocomplete="list", aria-expanded 자동 적용

터치 타겟#

  • 최소 터치 타겟 크기: 48x48dp
  • 제안 항목 높이: 최소 44dp로 터치 오동작 방지

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

v3.0부터 기본 API (enabled, onChanged, value 등)가 통일되었습니다. 아래는 플랫폼 고유 차이점만 나열합니다.

항목FlutterWeb
클래스명CouiAutoCompleteAutoComplete
라벨 속성labelTextlabel
드롭다운 위치입력 필드 아래 오버레이position: absolute CSS
필터 방식Dart 함수JavaScript 함수
  • Input: 자동 완성 없이 단순 텍스트 입력이 필요한 경우
  • Select: 고정된 옵션 목록에서만 선택해야 하는 경우
  • ChipInput: 자동 완성과 함께 여러 항목을 태그로 추가해야 하는 경우

조합 예제#

// AutoComplete + Form 조합: 사용자 검색 후 선택
CouiForm(
  child: Column(
    children: [
      CouiAutoComplete(
        asyncSearch: (query) => userRepository.searchUsers(query),
        onSelected: handleUserSelected,
        labelText: '담당자 검색',
        debounceMs: 300,
      ),
      CouiInput(
        label: '이메일',
        onChanged: handleEmailChanged,
      ),
    ],
  ),
)