Autocomplete
입력 중 추천 목록을 보여주는 자동 완성 입력 컴포넌트
목차
텍스트 입력 중 필터링된 추천 목록을 드롭다운으로 보여주는 자동 완성 컴포넌트입니다. 입력값으로 suggestions를 대소문자 무시 contains
매칭으로 필터링하고, 추천을 선택하면 mode에 따라 적용합니다.
기본 사용법 (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,
),
),
);
}
}
Live Preview#
코드 보기
class AutocompleteDefaultExample extends StatefulComponent {
const AutocompleteDefaultExample({super.key});
@override
State<AutocompleteDefaultExample> createState() =>
_AutocompleteDefaultExampleState();
}
class _AutocompleteDefaultExampleState
extends State<AutocompleteDefaultExample> {
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),
);
}
}
class AutocompleteDefaultExample extends StatefulWidget {
const AutocompleteDefaultExample({super.key});
@override
State<AutocompleteDefaultExample> createState() =>
_AutocompleteDefaultExampleState();
}
class _AutocompleteDefaultExampleState
extends State<AutocompleteDefaultExample> {
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),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 자유 입력이 가능하면서 추천을 보조로 제공할 때
- 검색 입력에 추천어를 함께 노출할 때
대신 다른 컴포넌트를 사용하세요:
추천 선택이 항상 입력값을 통째로 교체하기만 하면 되는 경우는
Autocomplete(mode: CoreAutocompleteMode.replaceAll)을 쓰세요 (옛Datalist는 이 모드로 통합됐습니다).
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
suggestions |
List<String> |
필수 | 추천 문자열 목록 |
value | String? | null | 현재 입력 값 |
placeholder |
String? |
null |
입력 안내 문구 |
enabled | bool | true | 입력 가능 여부 |
required | bool | false | 필수 입력 여부 |
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) for the part of the label that does not match the query. Text colour is carried via [CoreTextStyle.color] inside this slot. |
matchTextStyle |
CoreTextStyle? |
Text style override for the part of the label that matches the query (SemiBold
onSurface
by default). Merged over the label's base style, so a caller states only what differs.
|
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)#
-
필터링:
replaceWord는 커서가 있는 단어, 다른 모드는 입력 전체를 trim한 뒤 대소문자 무시contains로 찾습니다. 검색어가 비면 전체 목록이고 개수 상한은 없습니다. -
일치 구간 강조: 각 추천 글자에서 검색어(trim, 대소문자 무시)와 처음 일치하는 구간은 SemiBold
onSurface, 나머지는 RegularonSurfaceVariant로 그립니다. 일치가 글자의 앞 · 중간 · 뒤 어디에 있어도 같고, 검색어가 비어 있으면 강조하지 않습니다. 구간 계산은CoreAutocompleteMatch하나를 두 플랫폼이 읽고, 일치 구간의 글자 스타일은matchTextStyle로 덮을 수 있습니다. 호버 · 키보드 강조 행도 같은 구간을 강조합니다(일치하지 않는 글자는 호버 / 키보드 강조에서도onSurfaceVariant그대로이고, 일치 구간은 굵기로만 구분합니다). -
드롭다운 표시: 입력이 포커스되고
enabled가 참이며 추천이 하나 이상일 때 열립니다. blur · 외부 클릭 ·Escape· 선택은 패널을 닫습니다. 비동기로suggestions가 바뀌어도 다시 평가합니다. -
추천 적용 모드:
replaceAll: 입력 전체를 추천으로 교체replaceWord: 커서 위치의 단어만 교체하고 나머지 문자열 유지append: 구분자를 자동으로 추가하지 않고 기존 입력 뒤에 추천을 연결
-
콜백 순서: 타이핑은
onChanged(입력값), 추천 확정은onChanged(적용 후 문자열)→onSelected(추천 원문)→ 패널 닫힘입니다. 선택 직후 부모의 값 갱신만으로 패널을 다시 열지 않으며 다음 입력이나 재포커스에서 다시 열 수 있습니다. -
키보드: 열린 목록에서
ArrowDown/ArrowUp은 끝에서 순환합니다. 아직 강조 항목이 없으면 아래는 첫 항목, 위는 마지막 항목을 가리킵니다.Enter는 강조 항목이 있을 때만 확정하며, 없으면 입력 필드의 제출 경로를 유지합니다.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
추천 적용 방식에 맞는 mode 를 명시적으로 선택
Autocomplete(
suggestions: tags,
mode: CoreAutocompleteMode.append,
onSelected: (value) => addTag(value),
)
태그 조합처럼 선택한 추천을 기존 입력 뒤에 덧붙여야 한다면 append를 명시하세요. 기본값 replaceWord는 커서가 있는 단어만 교체합니다. append 는 구분자까지 만들어 주지는 않습니다.
❌ Don't#
suggestions에 크고 정제되지 않은 목록을 그대로 넘기지 않기
// ❌ 수백 개 항목을 그대로 전달
Autocomplete(
suggestions: allCountryNames,
onSelected: handleSelected,
)
필터링은 매 입력마다 suggestions 전체를 대소문자 무시 contains 로 순회하는 단순 스캔이며, 입력이 비어 있으면 캡(cap) 없이 전체 목록이 그대로 드롭다운에 표시됩니다. 목록이 크면 호출부에서 미리 좁혀서 넘기세요.
접근성 (Accessibility)#
역할#
-
Web — 입력에
role="combobox",aria-expanded,aria-controls,aria-autocomplete="list"를 붙입니다. 열린 목록의 강조 항목은aria-activedescendant로 연결하고 패널은role="listbox", 옵션은role="option"/aria-selected로 나타냅니다. 패널 표면은Popover를 합성합니다. -
Flutter — 컨테이너
Semantics와 Popover의 펼침 상태, 강조된 옵션의selected플래그를 씁니다. Flutter에는 combobox / listbox / option 역할이 없으며 옵션의 탭 액션은GestureDetector에서 옵니다.
키보드#
| 키 | Flutter | Web |
|---|---|---|
Escape |
입력의 조상 Focus 가 패널 닫음 |
합성된 Popover 가 패널 닫음 |
ArrowUp/ArrowDown 목록 탐색 | 순환 강조 이동 | 순환 강조 이동 |
Enter 로 강조 항목 확정 | 강조 항목이 있을 때 확정 | 강조 항목이 있을 때 확정 |
목록 탐색 중 실제 포커스는 입력에 남습니다. 포인터로도 추천을 선택할 수 있습니다.
브라우저 검증 — 이 컴포넌트의 Web 키보드 이동은 야간 파리티 스윕의 키보드 프로브(
tool/parity/keyboard_probe.dart)가 하이드레이션된 문서 사이트에서 실제로 키를 눌러, 입력의aria-activedescendant가 기대한 옵션을 가리키는지(닫힌 뒤에는 아무것도 가리키지 않는지) 확인합니다. 핸들러가 달렸는지가 아니라 결과를 봅니다.
스크린 리더#
옵션 이름은 추천 텍스트에서 옵니다. Web은 입력에서 활성 옵션을 참조하고 Flutter는 강조 옵션의 선택 플래그를 노출합니다. 입력 이름을 위한 전용 label
슬롯은 없으며 추천 패널에도 별도 이름이 없습니다.
live region 이 없습니다 — 필터 결과 개수, 목록이 열리고 닫힌 사실, 선택이 적용된 사실을 별도 live region으로 통보하지는 않습니다. 펼침 상태와 강조 항목은 위 역할·선택 속성으로 노출합니다.
포커스 관리#
- 진입 — 이동 없음. 패널이 열려도 포커스는 입력에 머뭅니다. (Flutter 오버레이에 autofocus 배선 자체는 있으나, 입력이 이미 포커스를 쥐고 있어 발화하지 않습니다.)
- 이탈 — 복귀 로직이 필요 없습니다(포커스가 트리거를 떠난 적이 없음). 반대로 포커스가 떠나면 패널이 닫힙니다.
-
트랩 — 없음(non-modal). Web 옵션은
<button>이라 DOM 상 포커스 가능하지만, 입력에서Tab을 누르는 순간 패널이 닫혀 실제로는 도달할 수 없습니다.
알려진 제약#
-
Web 의
Escape는 상태로 기억되지 않아 이후 입력에서 패널이 다시 열립니다(닫힘 애니메이션이 진행 중인 동안의 입력은 반영되지 않습니다). - 필터 결과에 상한이 없습니다 — 검색어가 비면 넘긴 목록 전체가 옵션이 됩니다.
-
Web
<input>은 이 경로에서autocomplete속성을 내보내지 않습니다(억제도 opt-in 도 하지 않습니다). - Flutter는 Web의 combobox / listbox 역할 대신 펼침 · 선택 플래그를 전달합니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | Autocomplete | Autocomplete |
| 입력 필드 | TextField (EditableText) |
TextField (<input>) |
| 드롭다운 | Popover 떠있는 패널 | Popover 떠있는 패널 |
| 활성 옵션 의미 | Semantics(selected:) |
입력의 aria-activedescendant + 옵션 aria-selected |
| 선택 후 커서 | TextEditingController 에 적용 위치 명시 |
입력값 재렌더 뒤 DOM 커서는 브라우저가 배치 |
추천 드롭다운은 양 플랫폼 모두 통일 Popover의 떠있는 패널로 렌더되어, 조상의 overflow/transform
클리핑을 벗어나고 뷰포트 안에 머물도록 flip/shift 됩니다 (Select와 동일). 패널 box chrome(배경·border·radius·shadow)은
autocompleteStyle.popoverStyle(→ CorePopupStyle) 단일 출처로 흐릅니다.