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];
18
19class SelectExample extends StatelessWidget {
20 @override
21 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}
29

CLI

To generate and customize this style:

dart run forui style create select

Usage

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@override
2Widget 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);
26

Sections

1@override
2Widget 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);
75

Dividers

1@override
2Widget 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);
24

Searchable

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];
18
19class SyncSelectExample extends StatelessWidget {
20 @override
21 Widget build(BuildContext context) => FSelect<String>.searchBuilder(
22 hint: 'Select a fruit',
23 format: (s) => s,
24 filter: (query) => query.isEmpty
25 ? fruits
26 : 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}
32

Async

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];
18
19class AsyncSelectExample extends StatelessWidget {
20 @override
21 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.isEmpty
27 ? fruits
28 : 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}
37

Async 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];
18
19class AsyncLoadingSelectExample extends StatelessWidget {
20 @override
21 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.isEmpty
27 ? fruits
28 : 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}
44

Async 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];
18
19class AsyncErrorSelectExample extends StatelessWidget {
20 @override
21 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}
44

Behavior

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];
18
19class ToggleableSelectExample extends StatelessWidget {
20 @override
21 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}
30

Clearable

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];
18
19class ClearableSelectExample extends StatelessWidget {
20 @override
21 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}
30

Custom Formatting

1@override
2Widget 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 );
11

Form

1class FormSelectExample extends StatefulWidget {
2 @override
3 State<FormSelectExample> createState() => _FormSelectExampleState();
4}
5
6class _FormSelectExampleState extends State<FormSelectExample>
7 with SingleTickerProviderStateMixin {
8 final _key = GlobalKey<FormState>();
9
10 @override
11 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

On this page