Autocomplete | CoUI
LogoCoUI

Autocomplete

입력 중 추천 목록을 보여주는 자동 완성 입력 컴포넌트

Autocomplete#

텍스트 입력 중 필터링된 추천 목록을 드롭다운으로 보여주는 자동 완성 컴포넌트입니다. 입력값으로 suggestions를 대소문자 무시 contains 매칭으로 필터링하고, 추천을 선택하면 mode에 따라 적용합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 자유 입력이 가능하면서 추천을 보조로 제공할 때
  • 검색 입력에 추천어를 함께 노출할 때

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

  • Select: 미리 정의된 옵션 중에서만 선택해야 할 때

추천 선택이 항상 입력값을 통째로 교체하기만 하면 되는 경우는 Autocomplete(mode: CoreAutocompleteMode.replaceAll)을 쓰세요 (옛 Datalist는 이 모드로 통합됐습니다).

기본 사용법 (Basic Usage)#

// 기본 자동 완성
Autocomplete(
  suggestions: ['Apple', 'Banana', 'Cherry'],
  placeholder: 'Search fruits',
  onSelected: (value) => print('Selected: value'),
)

// 추천 적용 모드 지정
Autocomplete(
  suggestions: tags,
  mode: CoreAutocompleteMode.append,
  onSelected: handleSelected,
)
// 기본 자동 완성
Autocomplete(
  suggestions: ['Apple', 'Banana', 'Cherry'],
  placeholder: 'Search fruits',
  onSelected: (value) => print('Selected: value'),
)

// 추천 적용 모드 지정
Autocomplete(
  suggestions: tags,
  mode: CoreAutocompleteMode.append,
  onSelected: handleSelected,
)

빠른 오버라이드 (Chain)#

이미 만든 Autocomplete 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class AutocompleteChainExample extends StatefulWidget {
  const AutocompleteChainExample({super.key});

  @override
  State<AutocompleteChainExample> createState() => _AutocompleteChainExampleState();
}

class _AutocompleteChainExampleState extends State<AutocompleteChainExample> {
  String _value = '';

  @override
  Widget build(BuildContext context) {
    return Autocomplete(
      suggestions: const ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
      value: _value,
      placeholder: 'Search fruits...',
      onSelected: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreAutocompleteStyle(
        dropdownMaxHeight: CoreSpace.space160,
        optionHoverColor: CoreColor.token(CoreColors.primary),
        optionHoverTextStyle: CoreTextStyle.token(
          CoreTextStyles.bodySmall,
          color: CoreColor.token(CoreColors.onPrimary),
        ),
        optionBorderRadius: CoreBorderRadius.all(CoreRadius.radius8),
        optionPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space12,
          vertical: CoreSpace.space8,
        ),
      ),
    );
  }
}
class AutocompleteChainExample extends StatefulComponent {
  const AutocompleteChainExample({super.key});

  @override
  State<AutocompleteChainExample> createState() => _AutocompleteChainExampleState();
}

class _AutocompleteChainExampleState extends State<AutocompleteChainExample> {
  String _value = '';

  @override
  Component build(BuildContext context) {
    return Autocomplete(
      suggestions: const ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
      value: _value,
      placeholder: 'Search fruits...',
      onSelected: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreAutocompleteStyle(
        dropdownMaxHeight: CoreSpace.space160,
        optionHoverColor: CoreColor.token(CoreColors.primary),
        optionHoverTextStyle: CoreTextStyle.token(
          CoreTextStyles.bodySmall,
          color: CoreColor.token(CoreColors.onPrimary),
        ),
        optionBorderRadius: CoreBorderRadius.all(CoreRadius.radius8),
        optionPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space12,
          vertical: CoreSpace.space8,
        ),
      ),
    );
  }
}

Props / Parameters#

속성타입기본값설명
suggestions List<String> 필수 추천 문자열 목록
valueString?null현재 입력 값
placeholder String? null 입력 안내 문구
enabledbooltrue입력 가능 여부
requiredboolfalse필수 입력 여부
name String? null 폼 제출용 필드 이름
mode CoreAutocompleteMode replaceWord 추천 적용 방식
onChanged ValueChanged<String>? null 입력 값 변경 콜백
onSelected ValueChanged<String>? null 추천 선택 콜백
autocompleteStyle CoreAutocompleteStyle? null 드롭다운 chrome 오버라이드

스타일 시스템 (Style System)#

CoreAutocompleteStyle 필드#

필드타입설명
popoverStyle CorePopoverStyle? Popover style forwarded to the composed Popover wrapping the input (trigger) + suggestion panel. Drives the trigger↔panel gap and the panel box chrome (border / radius / shadow / padding via popoverStyle.panelStyle , a CorePopupStyle ). null leaves the panel at the design-system Popup defaults.
dropdownTextStyle CoreTextStyle? Suggestion / option text style override (enabled / non-hovered). Text colour is carried via [CoreTextStyle.color] inside this slot.
dropdownMaxHeight double? Suggestion-list maximum height override (logical px). Constrains the option list, not the panel box (which is owned by the composed Popover ). Defaults to [defaultDropdownMaxHeight].
optionHoverColor CoreColor? Option hover background colour override.
optionHoverTextStyle CoreTextStyle? Option text style override applied when the option is hovered. Text colour is carried via [CoreTextStyle.color] inside this slot.
optionBorderRadius CoreBorderRadius? Option row border radius override.
optionPadding CoreEdgeInsets? Option row padding override.

동작 스펙 (Behavior)#

  • 필터링: 입력값으로 추천 목록을 대소문자 무시 contains 매칭. 입력이 비면 전체 노출
  • 드롭다운 표시: 포커스 시 열림, 외부 클릭 / blur 시 닫힘
  • 추천 적용 모드:
    • replaceAll / replaceWord: 입력 전체를 추천으로 교체
    • append: 기존 입력 뒤에 추천을 덧붙임

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

✅ Do#

추천 적용 방식에 맞는 mode 를 명시적으로 선택

Autocomplete(
  suggestions: tags,
  mode: CoreAutocompleteMode.append,
  onSelected: (value) => addTag(value),
)

태그 조합처럼 선택한 추천을 기존 입력 뒤에 덧붙여야 한다면 append를 명시하세요. 기본값 replaceWord는 입력 전체(또는 단어)를 교체하므로 태그 목록 조합에는 맞지 않습니다.


❌ Don't#

suggestions에 크고 정제되지 않은 목록을 그대로 넘기지 않기

// ❌ 수백 개 항목을 그대로 전달
Autocomplete(
  suggestions: allCountryNames,
  onSelected: handleSelected,
)

필터링은 매 입력마다 suggestions 전체를 대소문자 무시 contains 로 순회하는 단순 스캔이며, 입력이 비어 있으면 캡(cap) 없이 전체 목록이 그대로 드롭다운에 표시됩니다. 목록이 크면 호출부에서 미리 좁혀서 넘기세요.

접근성 (Accessibility)#

역할#

  • Web — 패널 role="listbox", 각 옵션은 <button role="option" data-value>. 단 패널은 Popover 가 감싸므로 그 버블에 role="dialog" 가 함께 붙고, 트리거는 aria-haspopup="dialog" + aria-expanded 를 알립니다. 즉 listbox 가 dialog 안에 중첩됩니다. <input> 자체에는 ARIA 가 없습니다.
  • Flutter — role 없음. 컨테이너 Semantics 만 있고, 옵션 행은 GestureDetector탭 액션은 노출되지만 role·선택 상태·자체 라벨이 없습니다.

키보드#

FlutterWeb
Escape없음패널 닫힘
ArrowUp/ArrowDown 목록 탐색 없음 없음
Enter 로 강조 항목 확정없음없음

목록을 키보드로 훑는 표준 combobox 조작이 양쪽 모두 없습니다. 옵션 선택은 포인터로만 가능합니다.

Escape 비대칭의 원인: Flutter 는 오버레이가 modal 일 때만 Escape 를 배선하는데 Autocomplete 는 non-modal 입니다. Web 은 modal 여부와 무관하게 동작합니다.

스크린 리더#

읽히는 이름은 옵션의 텍스트 노드뿐입니다. aria-label/semanticLabel 을 내보내는 자리가 양쪽 다 없고, listbox 자체에도 이름이 없습니다. Web 입력의 접근 가능한 이름은 placeholder 속성이 유일한 후보이며 <label for> 연결은 없습니다. Flutter 입력에는 이름이 아예 없습니다.

live region 이 없습니다 — 필터 결과 개수, 목록이 열리고 닫힌 사실, 선택이 적용된 사실 중 어느 것도 통보되지 않습니다. 상태로 노출되는 것은 트리거의 aria-expanded (Web) / expanded:(Flutter) 하나뿐입니다.

포커스 관리#

  • 진입 — 이동 없음. 패널이 열려도 포커스는 입력에 머뭅니다. (Flutter 오버레이에 autofocus 배선 자체는 있으나, 입력이 이미 포커스를 쥐고 있어 발화하지 않습니다.)
  • 이탈 — 복귀 로직이 필요 없습니다(포커스가 트리거를 떠난 적이 없음). 반대로 포커스가 떠나면 패널이 닫힙니다.
  • 트랩 — 없음(non-modal). Web 옵션은 <button> 이라 DOM 상 포커스 가능하지만, 입력에서 Tab 을 누르는 순간 패널이 닫혀 실제로는 도달할 수 없습니다.

알려진 제약#

  • Web 의 Escape 는 상태로 기억되지 않아 이후 입력에서 패널이 다시 열립니다(닫힘 애니메이션이 진행 중인 동안의 입력은 반영되지 않습니다).
  • 필터 결과에 상한이 없습니다 — 입력이 비면 넘긴 목록 전체가 옵션이 됩니다.
  • Web <input> 은 이 경로에서 autocomplete 속성을 내보내지 않습니다(억제도 opt-in 도 하지 않습니다).
  • Flutter 패널은 역할을 알리지 않아 리더에게 이름 없는 텍스트 묶음으로 노출됩니다.

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

항목FlutterWeb
클래스명AutocompleteAutocomplete
입력 필드 TextField (EditableText) TextField (<input>)
드롭다운Popover 떠있는 패널Popover 떠있는 패널

추천 드롭다운은 양 플랫폼 모두 통일 Popover의 떠있는 패널로 렌더되어, 조상의 overflow/transform 클리핑을 벗어나고 뷰포트 안에 머물도록 flip/shift 됩니다 (Select와 동일). 패널 box chrome(배경·border·radius·shadow)은 autocompleteStyle.popoverStyle(→ CorePopupStyle) 단일 출처로 흐릅니다.