Select
Displays a list of options for the user to pick from—triggered by a button.
final fruits = { 'apple': 'Apple', 'banana': 'Banana', 'blueberry': 'Blueberry', 'grapes': 'Grapes', 'pineapple': 'Pineapple',};
class SelectExample extends StatelessWidget { const SelectExample({super.key});
@override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return ConstrainedBox( constraints: const BoxConstraints(minWidth: 180), child: ShadSelect<String>( placeholder: const Text('Select a fruit'), options: [ Padding( padding: const EdgeInsets.fromLTRB(32, 6, 6, 6), child: Text( 'Fruits', style: theme.textTheme.muted.copyWith( fontWeight: FontWeight.w600, color: theme.colorScheme.popoverForeground, ), textAlign: TextAlign.start, ), ), ...fruits.entries .map((e) => ShadOption(value: e.key, child: Text(e.value))), ], selectedOptionBuilder: (context, value) => Text(fruits[value]!), onChanged: print, ), ); }}
Scrollable
final timezones = { 'North America': { 'est': 'Eastern Standard Time (EST)', 'cst': 'Central Standard Time (CST)', 'mst': 'Mountain Standard Time (MST)', 'pst': 'Pacific Standard Time (PST)', 'akst': 'Alaska Standard Time (AKST)', 'hst': 'Hawaii Standard Time (HST)', }, 'Europe & Africa': { 'gmt': 'Greenwich Mean Time (GMT)', 'cet': 'Central European Time (CET)', 'eet': 'Eastern European Time (EET)', 'west': 'Western European Summer Time (WEST)', 'cat': 'Central Africa Time (CAT)', 'eat': 'Eastern Africa Time (EAT)', }, 'Asia': { 'msk': 'Moscow Time (MSK)', 'ist': 'India Standard Time (IST)', 'cst_china': 'China Standard Time (CST)', 'jst': 'Japan Standard Time (JST)', 'kst': 'Korea Standard Time (KST)', 'ist_indonasia': 'Indonesia Standard Time (IST)', }, 'Australia & Pacific': { 'awst': 'Australian Western Standard Time (AWST)', 'acst': 'Australian Central Standard Time (ACST)', 'aest': 'Australian Eastern Standard Time (AEST)', 'nzst': 'New Zealand Standard Time (NZST)', 'fjt': 'Fiji Time (FJT)', }, 'South America': { 'art': 'Argentina Time (ART)', 'bot': 'Bolivia Time (BOT)', 'brt': 'Brasilia Time (BRT)', 'clt': 'Chile Standard Time (CLT)', },};
List<Widget> getTimezonesWidgets(ShadThemeData theme) { final widgets = <Widget>[]; for (final zone in timezones.entries) { widgets.add( Padding( padding: const EdgeInsets.fromLTRB(32, 6, 6, 6), child: Text( zone.key, style: theme.textTheme.muted.copyWith( fontWeight: FontWeight.w600, color: theme.colorScheme.popoverForeground, ), textAlign: TextAlign.start, ), ), ); widgets.addAll(zone.value.entries .map((e) => ShadOption(value: e.key, child: Text(e.value)))); } return widgets;}
class SelectExample extends StatelessWidget { const SelectExample({super.key});
@override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return ConstrainedBox( constraints: const BoxConstraints(minWidth: 280), child: ShadSelect<String>( placeholder: const Text('Select a timezone'), options: getTimezonesWidgets(theme), selectedOptionBuilder: (context, value) { final timezone = timezones.entries .firstWhere((element) => element.value.containsKey(value)) .value[value]; return Text(timezone!); }, ), ); }}
Form
final verifiedEmails = [];
class SelectFormField extends StatelessWidget { const SelectFormField({super.key});
@override Widget build(BuildContext context) { return ShadSelectFormField<String>( id: 'email', minWidth: 350, initialValue: null, options: verifiedEmails .map((email) => ShadOption(value: email, child: Text(email))) .toList(), selectedOptionBuilder: (context, value) => value == 'none' ? const Text('Select a verified email to display') : Text(value), placeholder: const Text('Select a verified email to display'), validator: (v) { if (v == null) { return 'Please select an email to display'; } return null; }, ); }}
With Search
const frameworks = {'nextjs': 'Next.js','svelte': 'SvelteKit','nuxtjs': 'Nuxt.js','remix': 'Remix','astro': 'Astro',};
class SelectWithSearch extends StatefulWidget { const SelectWithSearch({super.key});
@override State<SelectWithSearch> createState() => _SelectWithSearchState();}
class _SelectWithSearchState extends State<SelectWithSearch> { var searchValue = '';
Map<String, String> get filteredFrameworks => { for (final framework in frameworks.entries) if (framework.value.toLowerCase().contains(searchValue.toLowerCase())) framework.key: framework.value };
@override Widget build(BuildContext context) { return ShadSelect<String>.withSearch( minWidth: 180, maxWidth: 300, placeholder: const Text('Select framework...'), onSearchChanged: (value) => setState(() => searchValue = value), searchPlaceholder: const Text('Search framework'), options: [ if (filteredFrameworks.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 24), child: Text('No framework found'), ), ...frameworks.entries.map( (framework) { // this offstage is used to avoid the focus loss when the search results appear again // because it keeps the widget in the tree. return Offstage( offstage: !filteredFrameworks.containsKey(framework.key), child: ShadOption( value: framework.key, child: Text(framework.value), ), ); }, ) ], selectedOptionBuilder: (context, value) => Text(frameworks[value]!), ); }}
Multiple
This example shows how to select multiple options.
In addition, the allowDeselection
property is set to true
to allow the user to deselect an option and the closeOnSelect
property is set to false
to keep the popover open after selecting an option.
If you tap outside the popover, it will close.
final fruits = { 'apple': 'Apple', 'banana': 'Banana', 'blueberry': 'Blueberry', 'grapes': 'Grapes', 'pineapple': 'Pineapple',};
class SelectMultiple extends StatelessWidget { const SelectMultiple({super.key});
@override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return ShadSelect<String>.multiple( minWidth: 340, onChanged: print, allowDeselection: true, closeOnSelect: false, placeholder: const Text('Select multiple fruits'), options: [ Padding( padding: const EdgeInsets.fromLTRB(32, 6, 6, 6), child: Text( 'Fruits', style: theme.textTheme.large, textAlign: TextAlign.start, ), ), ...fruits.entries.map( (e) => ShadOption( value: e.key, child: Text(e.value), ), ), ], selectedOptionsBuilder: (context, values) => Text(values.map((v) => v.capitalize()).join(', ')), ); }}