A customizable Flutter dropdown — search, network search, multi-select and form validation built in.
dependencies:
dropdown_flutter: ^1.2.1import 'package:dropdown_flutter/custom_dropdown.dart';
DropdownFlutter<String>(
hintText: 'Select priority',
items: const ['Low', 'Medium', 'High', 'Urgent'],
initialItem: 'Medium', // optional
onChanged: (value) => print(value),
)That is the whole setup — no builders, controllers or config required.
| Constructor | Use it for |
|---|---|
DropdownFlutter() |
A plain list of items |
.search() |
Filtering a local list as the user types |
.searchRequest() |
Fetching results from an API |
.multiSelect() |
Selecting several items with checkboxes |
.multiSelectSearch() |
Multi-select over a filtered local list |
.multiSelectSearchRequest() |
Multi-select over API results |
Swap the constructor and keep the rest — every option below applies to all six.
The multiSelect* variants report through onListChanged; the rest use
onChanged.
Plain String items search out of the box. For your own type, mix in
CustomDropdownListFilter and decide what counts as a match.
class Member with CustomDropdownListFilter {
// toString() supplies the row label; filter() decides what matches.
@override
String toString() => name;
@override
bool filter(String q) => name.toLowerCase().contains(q.toLowerCase()) ||
role.toLowerCase().contains(q.toLowerCase()); // "engineer" finds them all
}
DropdownFlutter<Member>.search(items: members, onChanged: print);
DropdownFlutter<Member>.searchRequest( // same, but from an API
futureRequest: (query) async => api.searchMembers(query),
futureRequestDelay: const Duration(milliseconds: 300),
onChanged: print,
);final controller = SingleSelectController<String?>('Medium');
// MultiSelectController<String>(['Medium']) for multi-select
DropdownFlutter<String>(
items: priorities,
controller: controller, // read and set from anywhere
validator: (value) => value == null ? 'Required' : null,
validateOnChange: true, // listValidator for multi-select
onChanged: print,
);
controller.value = 'High';
controller.clear();toString() gives the default label. For anything richer, supply a
listItemBuilder — headerBuilder, hintBuilder and noResultFoundBuilder
work the same way.
DropdownFlutter<Member>(
items: members,
listItemBuilder: (context, item, isSelected, onItemSelect) => Row(
children: [
CircleAvatar(child: Text(item.name[0])),
const SizedBox(width: 12),
Text(item.name),
],
),
onChanged: print,
)All opt-in and off by default, so upgrading changes nothing.
| Property | Effect |
|---|---|
groupBy |
Splits the list into labelled sections |
highlightMatchedText |
Emphasises the matched substring in results |
recentSelectionsMaxCount |
Pins recently picked items to the top |
showSelectAll |
Adds a select-all / clear-all row (multi-select) |
selectAllText / clearAllText |
Relabel that row |
enableKeyboardNavigation |
Arrow keys move, Enter selects, Escape closes |
enableHapticFeedback |
Light impact on open, click on select |
animationDuration / animationCurve |
Tunes the open/close animation |
DropdownFlutter<Member>(
items: members,
groupBy: (member) => member.team, // any combination works
recentSelectionsMaxCount: 3,
enableKeyboardNavigation: true,
onChanged: print,
)decoration covers colors, borders, shadows and text styles; the builders
replace widgets outright. Colors fall back to the ambient ColorScheme, so
dropdowns follow a dark theme with no extra configuration.
DropdownFlutter<String>(
items: items,
decoration: CustomDropdownDecoration(
closedFillColor: const Color(0xFF1E1B33),
closedBorderRadius: BorderRadius.circular(16),
headerStyle: const TextStyle(color: Colors.white),
),
onChanged: print,
)
final withIcon = base.copyWith(prefixIcon: const Icon(Icons.person));Size is controlled by overlayHeight, listItemPadding and listItemHeight
— the last defaults to null so rows fit their content; setting it lets the list
scroll more efficiently.
| Group | Properties |
|---|---|
| Items | items, initialItem / initialItems, excludeSelected, and the onChanged / onListChanged callbacks |
| Text | hintText, searchHintText, noResultFoundText, maxlines (line limit on the closed header) |
| Sizing | listItemHeight, overlayHeight, listItemPadding / itemsListPadding, closedHeaderPadding / expandedHeaderPadding |
| Behaviour | enabled, canCloseOutsideBounds, hideSelectedFieldWhenExpanded, closeDropDownOnClearFilterSearch, visibility |
| Control | controller / multiSelectController, overlayController, itemsScrollController |
| Async | futureRequest / futureRequestDelay, searchRequestLoadingIndicator |
| Recents | initialRecentItems, onRecentItemsChanged |
| Validation | validator / listValidator, validateOnChange |
| Appearance | decoration / disabledDecoration |
| Builders | listItemBuilder, headerBuilder / headerListBuilder, hintBuilder, noResultFoundBuilder, groupHeaderBuilder |
Made by Farhan Sadik Galib
If this package saved you time, consider starring the repo ⭐
See the changelog for release notes. Released under the MIT License.



