Form
Select
A select displays a list of drop-down options for the user to pick from. It is a form-field and can therefore be used in a form.
For multi selections, consider using a multi select.
For touch devices, a select tile group or select menu tile is generally recommended over this.
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class SelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.rich(22 hint: 'Select a fruit',23 format: (s) => s,24 children: [25 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),26 ],27 );28}29CLI
To generate and customize this style:
dart run forui style create selectUsage
FSelect(...)
1FSelect<String>(2 style: const .delta(emptyTextStyle: .delta()),3 enabled: true,4 contentScrollHandles: true,5 items: const {'Apple': 'apple', 'Banana': 'banana', 'Cherry': 'cherry'},6)FSelect.rich(...)
1FSelect<String>.rich(2 style: const .delta(emptyTextStyle: .delta()),3 enabled: true,4 contentScrollHandles: true,5 format: (value) => value,6 children: [7 .item(title: const Text('Apple'), value: 'apple'),8 .item(title: const Text('Banana'), value: 'banana'),9 .section(10 label: const Text('More'),11 items: {'Cherry': 'cherry', 'Date': 'date', 'Elderberry': 'elderberry'},12 ),13 ],14)FSelect.search(...)
1FSelect<String>.search(2 style: const .delta(emptyTextStyle: .delta()),3 enabled: true,4 contentScrollHandles: true,5 filter: (query) =>6 ['apple', 'banana', 'cherry'].where((e) => e.startsWith(query)),7 items: const {'Apple': 'apple', 'Banana': 'banana', 'Cherry': 'cherry'},8)FSelect.searchBuilder(...)
1FSelect<String>.searchBuilder(2 style: const .delta(emptyTextStyle: .delta()),3 enabled: true,4 contentScrollHandles: true,5 format: (value) => value,6 filter: (query) =>7 ['apple', 'banana', 'cherry'].where((e) => e.startsWith(query)),8 contentBuilder: (context, style, values) => [9 for (final value in values) .item(title: Text(value), value: value),10 ],11)Examples
Detailed
1@override2Widget build(BuildContext context) => FSelect<String>.rich(3 hint: 'Type',4 format: (s) => s,5 children: [6 .item(7 prefix: const Icon(FIcons.bug),8 title: const Text('Bug'),9 subtitle: const Text('An unexpected problem or behavior'),10 value: 'Bug',11 ),12 .item(13 prefix: const Icon(FIcons.filePlusCorner),14 title: const Text('Feature'),15 subtitle: const Text('A new feature or enhancement'),16 value: 'Feature',17 ),18 .item(19 prefix: const Icon(FIcons.messageCircleQuestionMark),20 title: const Text('Question'),21 subtitle: const Text('A question or clarification'),22 value: 'Question',23 ),24 ],25);26Sections
1@override2Widget build(BuildContext context) => FSelect<String>.rich(3 hint: 'Select a timezone',4 format: (s) => s,5 children: [6 .section(7 label: const Text('North America'),8 items: {9 for (final item in [10 'Eastern Standard Time (EST)',11 'Central Standard Time (CST)',12 'Mountain Standard Time (MST)',13 'Pacific Standard Time (PST)',14 'Alaska Standard Time (AKST)',15 'Hawaii Standard Time (HST)',16 ])17 item: item,18 },19 ),20 .section(21 label: const Text('South America'),22 items: {23 for (final item in [24 'Argentina Time (ART)',25 'Bolivia Time (BOT)',26 'Brasilia Time (BRT)',27 'Chile Standard Time (CLT)',28 ])29 item: item,30 },31 ),32 .section(33 label: const Text('Europe & Africa'),34 items: {35 for (final item in [36 'Greenwich Mean Time (GMT)',37 'Central European Time (CET)',38 'Eastern European Time (EET)',39 'Western European Summer Time (WEST)',40 'Central Africa Time (CAT)',41 'Eastern Africa Time (EAT)',42 ])43 item: item,44 },45 ),46 .section(47 label: const Text('Asia'),48 items: {49 for (final item in [50 'Moscow Time (MSK)',51 'India Standard Time (IST)',52 'China Standard Time (CST)',53 'Japan Standard Time (JST)',54 'Korea Standard Time (KST)',55 'Indonesia Standard Time (IST)',56 ])57 item: item,58 },59 ),60 .section(61 label: const Text('Australia & Pacific'),62 items: {63 for (final item in [64 'Australian Western Standard Time (AWST)',65 'Australian Central Standard Time (ACST)',66 'Australian Eastern Standard Time (AEST)',67 'New Zealand Standard Time (NZST)',68 'Fiji Time (FJT)',69 ])70 item: item,71 },72 ),73 ],74);75Dividers
1@override2Widget build(BuildContext context) => FSelect<String>.rich(3 hint: 'Select a level',4 contentDivider: .full,5 format: (s) => s,6 children: [7 .section(8 label: const Text('Level 1'),9 divider: .indented,10 items: {11 for (final item in ['A', 'B']) item: '1$item',12 },13 ),14 .section(15 label: const Text('Level 2'),16 items: {17 for (final item in ['A', 'B']) item: '2$item',18 },19 ),20 .item(title: const Text('Level 3'), value: '3'),21 .item(title: const Text('Level 4'), value: '4'),22 ],23);24Searchable
Sync
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class SyncSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.searchBuilder(22 hint: 'Select a fruit',23 format: (s) => s,24 filter: (query) => query.isEmpty25 ? fruits26 : fruits.where((f) => f.toLowerCase().startsWith(query.toLowerCase())),27 contentBuilder: (context, _, fruits) => [28 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),29 ],30 );31}32Async
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class AsyncSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.searchBuilder(22 hint: 'Select a fruit',23 format: (s) => s,24 filter: (query) async {25 await Future.delayed(const Duration(seconds: 1));26 return query.isEmpty27 ? fruits28 : fruits.where(29 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),30 );31 },32 contentBuilder: (context, _, fruits) => [33 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),34 ],35 );36}37Async with Custom Loading
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class AsyncLoadingSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.searchBuilder(22 hint: 'Select a fruit',23 format: (s) => s,24 filter: (query) async {25 await Future.delayed(const Duration(seconds: 1));26 return query.isEmpty27 ? fruits28 : fruits.where(29 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),30 );31 },32 contentLoadingBuilder: (context, style) => Padding(33 padding: const .all(8.0),34 child: Text(35 'Here be dragons...',36 style: style.fieldStyle.contentTextStyle.resolve({}),37 ),38 ),39 contentBuilder: (context, _, fruits) => [40 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),41 ],42 );43}44Async with Custom Error Handling
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class AsyncErrorSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.searchBuilder(22 hint: 'Select a fruit',23 format: (s) => s,24 filter: (query) async {25 await Future.delayed(const Duration(seconds: 1));26 throw StateError('Error loading data');27 },28 contentBuilder: (context, _, fruits) => [29 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),30 ],31 contentErrorBuilder: (context, error, trace) {32 final style = context.theme.selectStyle.fieldStyle.iconStyle.resolve({});33 return Padding(34 padding: const .all(8.0),35 child: Icon(36 FIcons.messageCircleX,37 size: style.size,38 color: style.color,39 ),40 );41 },42 );43}44Behavior
Toggleable Items
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class ToggleableSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.rich(22 control: const .managed(initial: 'Apple', toggleable: true),23 hint: 'Select a fruit',24 format: (s) => s,25 children: [26 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),27 ],28 );29}30Clearable
1const fruits = [2 'Apple',3 'Banana',4 'Blueberry',5 'Grapes',6 'Lemon',7 'Mango',8 'Kiwi',9 'Orange',10 'Peach',11 'Pear',12 'Pineapple',13 'Plum',14 'Raspberry',15 'Strawberry',16 'Watermelon',17];1819class ClearableSelectExample extends StatelessWidget {20 @override21 Widget build(BuildContext context) => FSelect<String>.rich(22 hint: 'Select a fruit',23 format: (s) => s,24 clearable: true,25 children: [26 for (final fruit in fruits) .item(title: Text(fruit), value: fruit),27 ],28 );29}30Custom Formatting
1@override2Widget build(BuildContext context) =>3 FSelect<({String firstName, String lastName})>.rich(4 hint: 'Select a user',5 format: (user) => '${user.firstName} ${user.lastName}',6 children: [7 for (final user in users)8 .item(title: Text(user.firstName), value: user),9 ],10 );11Form
1class FormSelectExample extends StatefulWidget {2 @override3 State<FormSelectExample> createState() => _FormSelectExampleState();4}56class _FormSelectExampleState extends State<FormSelectExample>7 with SingleTickerProviderStateMixin {8 final _key = GlobalKey<FormState>();910 @override11 Widget build(BuildContext context) => Form(12 key: _key,13 child: Column(14 crossAxisAlignment: .start,15 spacing: 25,16 children: [17 FSelect<String>.rich(18 label: const Text('Department'),19 description: const Text('Choose your dream department'),20 hint: 'Select a department',21 format: (s) => s,22 validator: (department) =>23 department == null ? 'Please select a department' : null,24 children: [25 for (final department in const [26 'Engineering',27 'Marketing',28 'Sales',29 'Human Resources',30 'Finance',31 ])32 .item(title: Text(department), value: department),33 ],34 ),35 FButton(36 child: const Text('Submit'),37 onPress: () {38 if (_key.currentState!.validate()) {39 // Form is valid, do something with department.40 }41 },42 ),43 ],44 ),45 );46}47