Adaptive Search Dropdown
Created by Rahul Khatri
A mobile-friendly, adaptive search dropdown widget for Flutter with support for single/multi-selection, infinite scrolling, asynchronous search, and full UI customisation.
Features
- Mobile First — overlay panel adapts to keyboard and available screen height, flips above the field when needed
- Searchable — optional search bar with 400ms debounce for remote filtering
- Single & Multi-Select — built-in support for both modes
- Pagination — auto-loads more items on scroll via
onLoadMore - "Add New" Row — lets users create items that are not in the list
- Read-Only Mode — greyed-out field that cannot be opened
- Full UI Customisation — every colour, text style, icon, padding, and border is configurable via
AdaptiveDropdownStyle, or replace entire sections with builder callbacks
Getting Started
dependencies:
adaptive_search_dropdown: ^0.0.1
Usage
Basic (uses sensible defaults)
AdaptiveSearchDropdown<String>(
label: 'Select Country',
items: ['United States', 'Canada', 'Mexico', 'United Kingdom'],
itemAsString: (item) => item,
onChanged: (value) => print("Selected: $value"),
)
Multi-Select
AdaptiveSearchDropdown<String>(
label: 'Select Skills',
items: ['Flutter', 'Dart', 'Firebase', 'Swift'],
itemAsString: (item) => item,
multiSelect: true,
onSelectionChanged: (values) => print("Selected: $values"),
)
Async Data & Pagination
AdaptiveSearchDropdown<UserModel>(
label: 'Assign User',
items: users,
itemAsString: (user) => user.name,
itemId: (user) => user.id,
onTap: () async => await fetchUsers(page: 1),
onSearch: (query) async => await searchUsers(query),
onLoadMore: (page, query) async => await fetchUsers(page: page, search: query),
totalPages: totalPages,
)
Full UI Customisation
Option 1: AdaptiveDropdownStyle (recommended)
Pass your own colours, text styles, icons, paddings — everything is optional, defaults are used for anything you skip.
// Define your app's style once
final myDropdownStyle = AdaptiveDropdownStyle(
// Accent colour used for selected text, tick box, check icon, add-row
accentColor: Color(0xff03A4AA),
// Field
fieldTextStyle: TextStyle(fontSize: 14, fontFamily: 'MyFont'),
fieldBorderRadius: BorderRadius.circular(10),
fieldContentPadding: EdgeInsets.symmetric(horizontal: 14, vertical: 12),
readOnlyFillColor: Color(0xffF7F9FA),
readOnlyTextStyle: TextStyle(color: Color(0xff72879D)),
// Label
labelStyle: TextStyle(color: Color(0xff72879D)),
requiredAsteriskStyle: TextStyle(color: Color(0xffFF5247)),
// Error
errorBorderColor: Color(0xffFF5247),
errorTextStyle: TextStyle(color: Color(0xffFF5247), fontSize: 12),
// Suffix icons
arrowIcon: SvgPicture.asset("assets/images/dashboard/down_arrow.svg", height: 9),
clearIcon: Icon(Icons.close_rounded, size: 18, color: Color(0xff72879D)),
// Panel
panelBackgroundColor: Colors.white,
panelDarkBackgroundColor: Color(0xff161F28),
panelBorderColor: Color(0xffD2E3F4),
panelDarkBorderColor: Color(0xff243342),
panelBorderRadius: BorderRadius.circular(10),
// Rows
rowTextStyle: TextStyle(fontSize: 13),
selectedRowTextStyle: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xff03A4AA)),
selectedRowColor: Color(0xffD9F1F2), // light mode
selectedRowDarkColor: Color(0xff03A4AA).withOpacity(0.12), // dark mode
rowPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
// Search bar
searchTextStyle: TextStyle(fontSize: 14),
searchHintStyle: TextStyle(color: Color(0xff72879D)),
searchCursorColor: Color(0xff03A4AA),
// Create/Add row
createRowTextStyle: TextStyle(fontWeight: FontWeight.w600, color: Color(0xff03A4AA)),
createRowBackgroundColor: Color(0xffD9F1F2),
// Empty state
emptyTextStyle: TextStyle(color: Color(0xff72879D)),
);
// Use it
AdaptiveSearchDropdown<LeadModel>(
style: myDropdownStyle,
label: 'Lead Name',
items: leads,
itemAsString: (lead) => lead.name,
itemId: (lead) => lead.id,
onChanged: (value) => ...,
)
Option 2: Builder Callbacks (full UI replacement)
For total control over a specific part, use builder callbacks:
AdaptiveSearchDropdown<UserModel>(
label: 'Select User',
items: users,
itemAsString: (user) => user.name,
// Build each row yourself
itemBuilder: (item, isSelected, isMultiSelect) {
return ListTile(
leading: CircleAvatar(backgroundImage: NetworkImage(item.avatar)),
title: Text(item.name),
trailing: isSelected ? Icon(Icons.check, color: Colors.teal) : null,
);
},
// Build the closed field yourself
selectedFieldBuilder: (text, isOpen) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.teal),
borderRadius: BorderRadius.circular(8),
),
child: Text(text.isEmpty ? 'Pick a user...' : text),
);
},
// Custom empty state
emptyBuilder: (query) => Center(
child: Text('No users found for "$query"'),
),
// Custom loading
loadingBuilder: () => Center(child: Lottie.asset('assets/loading.json')),
onChanged: (value) => ...,
)
Option 3: Mix Both
Use AdaptiveDropdownStyle for colours and a builder for one specific piece:
AdaptiveSearchDropdown<UserModel>(
style: myDropdownStyle, // your colours and styles
itemBuilder: (item, isSelected, _) => MyCustomRow(item, isSelected), // custom row only
...
)
API Reference
| Property | Type | Description |
|---|---|---|
style |
AdaptiveDropdownStyle? |
Full visual customisation |
items |
List<T> |
The list of items to display |
label |
String |
Field label text |
itemAsString |
String Function(T) |
How to display each item |
itemId |
Object? Function(T)? |
Unique identity for each item |
itemSubtitle |
String? Function(T)? |
Optional second line per row |
initialItem |
T? |
Pre-selected item |
isRequired |
bool |
Shows * and validates non-empty |
readOnly |
bool |
Greys out field, prevents opening |
multiSelect |
bool |
Enables multi-selection mode |
initialItems |
List<T>? |
Pre-selected items for multi-select |
onChanged |
void Function(T?)? |
Called on single-select change |
onSelectionChanged |
void Function(List<T>)? |
Called on multi-select change |
onTap |
Future<List<T>> Function()? |
Load items when panel opens |
onSearch |
Future<List<T>> Function(String)? |
Server-side search |
onLoadMore |
Future<List<T>> Function(int, String)? |
Pagination |
totalPages |
int? |
Total pages from server |
canCreate |
bool |
Show "Add" row |
onCreate |
VoidCallback? |
Called when "Add" is tapped |
itemBuilder |
Widget Function(T, bool, bool)? |
Custom row builder |
selectedFieldBuilder |
Widget Function(String, bool)? |
Custom field builder |
emptyBuilder |
Widget Function(String)? |
Custom empty state |
loadingBuilder |
Widget Function()? |
Custom loading state |
Libraries
- adaptive_search_dropdown
- A mobile-friendly, adaptive search dropdown widget for Flutter.