Form
Autocomplete
An autocomplete provides a list of suggestions based on the user's input and shows typeahead text for the first match. It is a form-field and can therefore be used in a form.
Not a searchable select
An autocomplete is not a searchable select. it is a text-field with suggestions. Values are not limited to one of suggestions, users can type anything. If you need a searchable select, use a searchable select or a searchable multi select
1@override2Widget build(BuildContext _) => FAutocomplete(3 label: const Text('Autocomplete'),4 hint: 'What can it do?',5 items: features,6);7CLI
To generate a specific style for customization:
dart run forui style create autocompletedart run forui style create autocomplete-field-sizesdart run forui style create autocomplete-fielddart run forui style create autocomplete-contentdart run forui style create autocomplete-sectionUsage
FAutocomplete(...)
1FAutocomplete(2 style: const .delta(),3 size: .md,4 enabled: true,5 hint: 'Hint',6 items: const ['Apple', 'Banana', 'Cherry'],7)FAutocomplete.builder(...)
1FAutocomplete.builder(2 size: .md,3 style: const .delta(),4 enabled: true,5 filter: (query) => [6 'Apple',7 'Banana',8 ].where((item) => item.toLowerCase().startsWith(query.toLowerCase())),9 contentBuilder: (context, query, values) => [10 for (final value in values) .item(value: value),11 ],12)Examples
Detailed
1@override2Widget build(BuildContext _) => FAutocomplete.builder(3 hint: 'Type to search',4 filter: (query) => const [5 'Bug',6 'Feature',7 'Question',8 ].where((i) => i.toLowerCase().contains(query.toLowerCase())),9 contentBuilder: (context, query, suggestions) => [10 for (final suggestion in suggestions)11 switch (suggestion) {12 'Bug' => .item(13 value: 'Bug',14 prefix: const Icon(FIcons.bug),15 title: const Text('Bug'),16 subtitle: const Text('An unexpected problem or behavior'),17 ),18 'Feature' => .item(19 value: 'Feature',20 prefix: const Icon(FIcons.filePlusCorner),21 title: const Text('Feature'),22 subtitle: const Text('A new feature or enhancement'),23 ),24 'Question' => .item(25 value: 'Question',26 prefix: const Icon(FIcons.messageCircleQuestionMark),27 title: const Text('Question'),28 subtitle: const Text('A question or clarification'),29 ),30 _ => .item(value: suggestion),31 },32 ],33);34Sections
1const timezones = {2 'North America': [3 'Eastern Standard Time (EST)',4 'Central Standard Time (CST)',5 'Mountain Standard Time (MST)',6 'Pacific Standard Time (PST)',7 'Alaska Standard Time (AKST)',8 'Hawaii Standard Time (HST)',9 ],10 'South America': [11 'Argentina Time (ART)',12 'Bolivia Time (BOT)',13 'Brasilia Time (BRT)',14 'Chile Standard Time (CLT)',15 ],16 'Europe & Africa': [17 'Greenwich Mean Time (GMT)',18 'Central European Time (CET)',19 'Eastern European Time (EET)',20 'Western European Summer Time (WEST)',21 'Central Africa Time (CAT)',22 'Eastern Africa Time (EAT)',23 ],24 'Asia': [25 'Moscow Time (MSK)',26 'India Standard Time (IST)',27 'China Standard Time (CST)',28 'Japan Standard Time (JST)',29 'Korea Standard Time (KST)',30 'Indonesia Standard Time (IST)',31 ],32 'Australia & Pacific': [33 'Australian Western Standard Time (AWST)',34 'Australian Central Standard Time (ACST)',35 'Australian Eastern Standard Time (AEST)',36 'New Zealand Standard Time (NZST)',37 'Fiji Time (FJT)',38 ],39};4041class SectionAutocompleteExample extends StatelessWidget {42 @override43 Widget build(BuildContext _) => FAutocomplete.builder(44 hint: 'Type to search timezones',45 filter: (query) => timezones.values46 .expand((list) => list)47 .where(48 (timezone) => timezone.toLowerCase().contains(query.toLowerCase()),49 ),50 contentBuilder: (context, query, suggestions) => [51 for (final MapEntry(key: label, value: zones) in timezones.entries)52 if (zones.where(suggestions.contains).toList() case final zones53 when zones.isNotEmpty)54 .section(label: Text(label), items: zones),55 ],56 );57}58Dividers
1@override2Widget build(BuildContext _) => FAutocomplete.builder(3 hint: 'Type to search levels',4 filter: (query) => const [5 '1A',6 '1B',7 '2A',8 '2B',9 '3',10 '4',11 ].where((i) => i.toLowerCase().contains(query.toLowerCase())),12 contentBuilder: (context, query, suggestions) =>13 <FAutocompleteItemMixin>[14 .richSection(15 label: const Text('Level 1'),16 divider: .indented,17 children: [18 if (suggestions.contains('1A')) .item(value: '1A'),19 if (suggestions.contains('1B')) .item(value: '1B'),20 ],21 ),22 .section(23 label: const Text('Level 2'),24 items: ['2A', '2B'].where(suggestions.contains).toList(),25 ),26 if (suggestions.contains('3')) .item(value: '3'),27 if (suggestions.contains('4')) .item(value: '4'),28 ]29 .where(30 (item) => item is! FAutocompleteSection || item.children.isNotEmpty,31 )32 .toList(),33);34Behavior
Async
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete.builder(13 hint: 'Type to search fruits',14 filter: (query) async {15 await Future.delayed(const Duration(seconds: 3));16 return query.isEmpty17 ? fruits18 : fruits.where(19 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),20 );21 },22 contentBuilder: (context, query, values) => [23 for (final fruit in values) .item(value: fruit),24 ],25 );26}27Async with Custom Loading
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncLoadingAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete.builder(13 hint: 'Type to search fruits',14 filter: (query) async {15 await Future.delayed(const Duration(seconds: 3));16 return query.isEmpty17 ? fruits18 : fruits.where(19 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),20 );21 },22 contentLoadingBuilder: (context, style) => Padding(23 padding: const .all(14.0),24 child: Text('Here be dragons...', style: style.emptyTextStyle),25 ),26 contentBuilder: (context, query, suggestions) => [27 for (final suggestion in suggestions) .item(value: suggestion),28 ],29 );30}31Async with Custom Error Handling
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncErrorAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete.builder(13 hint: 'Type to search fruits',14 filter: (query) async {15 await Future.delayed(const Duration(seconds: 3));16 throw StateError('Error loading data');17 },18 contentBuilder: (context, query, values) => [19 for (final fruit in values) .item(value: fruit),20 ],21 contentErrorBuilder: (context, error, trace) => Padding(22 padding: const .all(14.0),23 child: Icon(24 FIcons.circleX,25 size: 15,26 color: context.theme.colors.primary,27 ),28 ),29 );30}31Clearable
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class ClearableAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete(13 hint: 'Type to search fruits',14 clearable: (value) => value.text.isNotEmpty,15 items: fruits,16 );17}18Popover Builder
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class PopoverBuilderAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete(13 hint: 'Type to search fruits',14 items: fruits,15 popoverBuilder: (context, controller, popoverController, content) =>16 SingleChildScrollView(17 child: Column(18 mainAxisSize: .min,19 children: [20 content,21 const FDivider(style: .delta(padding: .value(.zero))),22 FButton(23 variant: .ghost,24 prefix: const Icon(FIcons.list),25 child: const Text('Browse All'),26 onPress: () {},27 ),28 ],29 ),30 ),31 );32}33Form
1class FormAutocompleteExample extends StatefulWidget {2 @override3 State<FormAutocompleteExample> createState() =>4 _FormAutocompleteExampleState();5}67class _FormAutocompleteExampleState extends State<FormAutocompleteExample> {8 final _key = GlobalKey<FormState>();910 @override11 Widget build(BuildContext _) => Form(12 key: _key,13 child: Column(14 crossAxisAlignment: .start,15 spacing: 16,16 children: [17 FAutocomplete(18 label: const Text('Department'),19 description: const Text('Type to search your dream department'),20 hint: 'Search departments',21 validator: (department) => department == null || department.isEmpty22 ? 'Please select a department'23 : null,24 items: const [25 'Engineering',26 'Marketing',27 'Sales',28 'Human Resources',29 'Finance',30 ],31 ),32 Row(33 mainAxisAlignment: .end,34 children: [35 FButton(36 size: .sm,37 mainAxisSize: .min,38 child: const Text('Submit'),39 onPress: () {40 if (_key.currentState!.validate()) {41 // Form is valid, do something with department.42 }43 },44 ),45 ],46 ),47 ],48 ),49 );50}51