searchable_dropdown_pro 1.1.0
searchable_dropdown_pro: ^1.1.0 copied to clipboard
A highly flexible, production-ready searchable dropdown for Flutter. Supports custom text fields, custom validators, lazy-loaded items, composited overlay positioning, and toggleable search — all with [...]
searchable_dropdown_pro #
A highly flexible, production-ready searchable dropdown for Flutter with zero forced dependencies beyond the Flutter SDK itself.
Features #
- ✅ Bring your own TextField — or use the sensible built-in default.
- ✅ Bring your own validator — standard Flutter form-field API.
- ✅ Toggle search on/off —
isSearchable: falsefor a plain dropdown. - ✅ Custom filter logic — override the default substring match.
- ✅ Custom item tiles — full control over each row.
- ✅ Composited overlay — stays pinned inside
SingleChildScrollView. - ✅ Async / lazy items — pass a new
itemslist and the overlay refreshes. - ✅ Programmatic control — open, close, clear, or pre-fill via
SearchableDropdownController. - ✅ Full theming —
SearchableDropdownDecorationcovers every visual detail. - ✅ Null-safe — written for Dart 3 with sound null safety.
Getting started #
Add the dependency to your pubspec.yaml:
dependencies:
searchable_dropdown_pro: ^1.0.0
Then run:
flutter pub get
Usage #
Minimal #
import 'package:searchable_dropdown_pro/searchable_dropdown_pro.dart';
SearchableDropdownPro<String>(
items: ['Apple', 'Banana', 'Cherry'],
displayString: (item) => item,
onSelected: (item) => print('Selected: $item'),
hintText: 'Pick a fruit',
)
With a model class #
class Country {
final String name;
final String flag;
const Country(this.name, this.flag);
}
SearchableDropdownPro<Country>(
items: countries,
displayString: (c) => c.name,
onSelected: (c) => _onCountrySelected(c),
hintText: 'Select country',
)
Non-searchable dropdown #
Pass isSearchable: false and the list is shown as-is without a search
input — identical behaviour to a plain DropdownButton but with a
composited overlay and full theming.
SearchableDropdownPro<String>(
isSearchable: false,
items: ['Small', 'Medium', 'Large'],
displayString: (s) => s,
onSelected: (_) {},
hintText: 'Select size',
)
Bring your own TextField #
Supply fieldBuilder to render any widget as the trigger. The builder
receives the managed TextEditingController, FocusNode, and a
pre-built suffixIcon so you can embed your own design-system field:
SearchableDropdownPro<String>(
items: items,
displayString: (e) => e,
onSelected: (_) {},
hintText: 'Search…',
fieldBuilder: (context, controller, focusNode, suffixIcon) {
return MyDesignSystemTextField(
controller: controller,
focusNode: focusNode,
suffixIcon: suffixIcon,
hintText: 'Search…',
);
},
)
Bring your own validator #
SearchableDropdownPro<String>(
items: items,
displayString: (e) => e,
onSelected: (_) {},
hintText: 'Required field',
validator: (value) {
if (value == null || value.isEmpty) return 'Please select an option';
return null;
},
autovalidateMode: AutovalidateMode.onUserInteraction,
)
Custom item tiles #
SearchableDropdownPro<Country>(
items: countries,
displayString: (c) => c.name,
onSelected: (_) {},
hintText: 'Country',
itemBuilder: (context, country, isSelected, onTap) {
return ListTile(
leading: Text(country.flag, style: const TextStyle(fontSize: 24)),
title: Text(country.name),
selected: isSelected,
onTap: onTap,
);
},
)
Custom filter #
SearchableDropdownPro<String>(
items: items,
displayString: (e) => e,
onSelected: (_) {},
hintText: 'Starts-with search',
filterBuilder: (items, query) =>
items.where((e) => e.startsWith(query)).toList(),
)
Async / lazy-loaded items #
Just rebuild with a new items list — the overlay refreshes automatically:
SearchableDropdownPro<City>(
items: _cities, // starts empty, populated after API call
displayString: (c) => c.name,
onSelected: (_) {},
hintText: 'City',
)
Programmatic control #
final _ctrl = SearchableDropdownController();
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
// Widget
SearchableDropdownPro<String>(
items: items,
displayString: (e) => e,
onSelected: (_) {},
hintText: 'Pick one',
controller: _ctrl,
)
// Elsewhere in your code
_ctrl.open();
_ctrl.close();
_ctrl.clear();
_ctrl.setText('Pre-filled value');
Theming #
SearchableDropdownPro<String>(
items: items,
displayString: (e) => e,
onSelected: (_) {},
hintText: 'Themed',
decoration: SearchableDropdownDecoration(
dropdownColor: Colors.grey.shade900,
dropdownElevation: 8,
borderRadius: BorderRadius.circular(12),
itemTextStyle: const TextStyle(color: Colors.white),
selectedItemColor: Colors.blue.withOpacity(0.2),
dividerColor: Colors.grey.shade700,
noMatchText: 'Nothing found 🤷',
suffixIcon: const Icon(Icons.arrow_drop_down, color: Colors.blue),
),
)
API reference #
SearchableDropdownPro<T> #
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
List<T> |
required | Items to display. |
displayString |
String Function(T) |
required | Converts an item to its label. |
onSelected |
ValueChanged<T> |
required | Called on selection. |
hintText |
String |
required | Placeholder text. |
isSearchable |
bool |
true |
Enables typing to filter. |
enabled |
bool |
true |
Enables / disables the field. |
clearOnOpen |
bool |
true |
Clears field text when opened. |
clearAfterSelection |
bool |
false |
Clears field after selection. |
maxVisibleItems |
int |
5 |
Items visible before scrolling. |
fieldHeight |
double |
52 |
Height used to offset the overlay. |
selectedItem |
T? |
null |
Highlights the selected item in the list. |
filterBuilder |
List<T> Function(List<T>, String)? |
null |
Custom filter logic. |
itemBuilder |
Widget Function(…)? |
null |
Custom item tile builder. |
fieldBuilder |
Widget Function(…)? |
null |
Custom field widget builder. |
validator |
String? Function(String?)? |
null |
Form validator. |
autovalidateMode |
AutovalidateMode? |
null |
When to run the validator. |
decoration |
SearchableDropdownDecoration |
const SearchableDropdownDecoration() |
Visual configuration. |
controller |
SearchableDropdownController? |
null |
Programmatic control. |
onChanged |
ValueChanged<String>? |
null |
Text change callback. |
onDropdownToggle |
ValueChanged<bool>? |
null |
Open/close callback. |
textEditingController |
TextEditingController? |
null |
External text controller. |
focusNode |
FocusNode? |
null |
External focus node. |
SearchableDropdownController #
| Method | Description |
|---|---|
open() |
Opens the dropdown overlay. |
close() |
Closes the dropdown overlay. |
clear() |
Clears the text field and selection. |
setText(String) |
Pre-fills the field without triggering onSelected. |
SearchableDropdownDecoration #
| Property | Type | Default | Description |
|---|---|---|---|
dropdownColor |
Color? |
Theme surface | Overlay background. |
dropdownElevation |
double |
4 |
Shadow depth. |
borderRadius |
BorderRadius? |
8 |
Overlay corner radius. |
overlayVerticalOffset |
double |
4 |
Gap between field and overlay. |
itemPadding |
EdgeInsetsGeometry? |
H:12 V:10 |
Item tile padding. |
itemHeight |
double? |
52 |
Fixed item tile height. |
itemTextStyle |
TextStyle? |
bodyMedium | Item label style. |
noMatchText |
String |
'No match found' |
Empty-results message. |
showDivider |
bool |
true |
Show dividers between items. |
dividerColor |
Color? |
Theme divider | Divider colour. |
selectedItemColor |
Color? |
null |
Highlight colour for selected item. |
suffixIcon |
Widget? |
Arrow icon | Custom dropdown arrow. |
animateSuffixIcon |
bool |
true |
Rotate arrow on open. |
Additional information #
- File bugs and feature requests on the issue tracker.
- Contributions are welcome — please open a PR against
main. - This package follows semantic versioning.