dropdown_custom 0.4.0
dropdown_custom: ^0.4.0 copied to clipboard
A customizable, zero-dependency dropdown with search, grouping, per-item enable/disable, custom colors, and free positioning (top/bottom/left/right).
dropdown_custom #
A customizable, zero-dependency dropdown for Flutter. Simple to set up, yet scales to real-world needs: search, grouping, per-item enable/disable, custom colors, and free positioning — top, bottom, left, or right with auto-flip.
Works on every Flutter platform: Android · iOS · Web · Windows · macOS · Linux. It is a pure-Dart package (no native code, no platform channels, no third-party dependencies), so it runs anywhere Flutter runs.
Why another dropdown? #
Flutter's built-in dropdown is limited, and most alternatives only open below
the trigger. dropdown_custom focuses on:
- Free positioning — open the menu on any side, with
autoflipping when it would run off-screen. - Type-safe generics — items are a plain
List<T>; no wrapper class required.onChangedreturns yourT, notdynamic. - Simple by default — the basic case is three lines; every extra feature is an optional parameter.
- No dependencies — only the Flutter SDK.
Getting started #
Add it to your pubspec.yaml:
dependencies:
dropdown_custom: ^0.1.0
Usage #
Simplest case #
CustomDropdown<String>(
items: const ['Apple', 'Mango', 'Orange'],
onChanged: (value) => print(value),
)
Over your own model, with search and disabled items #
CustomDropdown<City>(
items: cities,
value: selected,
itemLabel: (c) => c.name,
isItemEnabled: (c) => c.available,
enableSearch: true,
onChanged: (c) => setState(() => selected = c),
)
Grouped #
CustomDropdown<City>(
items: cities,
itemLabel: (c) => c.name,
groupBy: (c) => c.province,
enableSearch: true,
onChanged: (c) => ...,
)
Multi-select #
CustomDropdown<City>.multi(
items: cities,
selectedItems: picked,
itemLabel: (c) => c.name,
groupBy: (c) => c.province,
enableSearch: true,
onSelectionChanged: (list) => setState(() => picked = list),
)
The menu shows a checkbox on each row and stays open while the user toggles
items; onSelectionChanged fires with the full selection on every change.
Add optional "select all" / "clear" actions with showSelectAll: true
(off by default). Both respect the active search filter and skip disabled
items, and their labels are customizable:
CustomDropdown<City>.multi(
items: cities,
selectedItems: picked,
itemLabel: (c) => c.name,
enableSearch: true,
showSelectAll: true,
selectAllLabel: 'Select all',
clearAllLabel: 'Clear',
onSelectionChanged: (list) => setState(() => picked = list),
)
Async loading #
CustomDropdown<User>.async(
loader: (query) => api.searchUsers(query), // Future<List<User>>
itemLabel: (u) => u.name,
debounce: const Duration(milliseconds: 300),
onChanged: (u) => setState(() => selected = u),
)
loader is called with the debounced search query and owns filtering, so
results are shown as-is. The menu handles the loading state, an error state
with a retry action, and the empty state for you.
Choose how the loading state looks with loading — a circular spinner, an
animated skeleton shimmer (both with customizable colors), or your own widget:
CustomDropdown<User>.async(
loader: (query) => api.searchUsers(query),
// 1) circular spinner with a custom color
loading: const DropdownLoading.circular(color: Colors.teal),
// 2) animated skeleton shimmer (zero dependencies)
// loading: DropdownLoading.shimmer(
// baseColor: Colors.grey.shade300,
// highlightColor: Colors.grey.shade100,
// itemCount: 5,
// ),
// 3) fully custom loading widget
// loading: DropdownLoading.custom((context) => const MyLoader()),
onChanged: (u) => ...,
)
The empty and error states are customizable too, so every async state (loading / empty / error) can use your own widgets:
CustomDropdown<User>.async(
loader: (query) => api.searchUsers(query),
emptyBuilder: (context) => const Center(child: Text('No users found')),
errorBuilder: (context, error, retry) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('$error'),
TextButton(onPressed: retry, child: const Text('Try again')),
],
),
onChanged: (u) => ...,
)
emptyBuilder also works on the single- and multi-select constructors (shown
when a search yields no matches).
Positioning #
CustomDropdown<City>(
items: cities,
itemLabel: (c) => c.name,
direction: DropdownDirection.right, // top / bottom / left / right / auto
onChanged: (c) => ...,
)
Styling (field / menu / search — independently) #
Styling is split into three groups so the input field, the menu box, and the search bar can be themed separately:
CustomDropdown<City>(
items: cities,
itemLabel: (c) => c.name,
enableSearch: true,
// The input field (trigger).
fieldStyle: DropdownFieldStyle(
backgroundColor: Colors.white,
borderColor: Colors.teal,
borderWidth: 1.5,
textStyle: const TextStyle(fontSize: 16, color: Colors.black87),
iconColor: Colors.teal,
),
// The dropdown box and its list rows.
menuStyle: DropdownMenuStyle(
backgroundColor: Colors.grey.shade50,
borderColor: Colors.teal.shade100,
itemTextStyle: const TextStyle(fontSize: 14),
selectedTextStyle: const TextStyle(fontWeight: FontWeight.bold),
highlightColor: Colors.teal.shade50,
maxHeight: 280,
),
// The search bar.
searchStyle: DropdownSearchStyle(
fillColor: Colors.grey.shade100,
borderColor: Colors.grey.shade300,
focusedBorderColor: Colors.teal,
hintStyle: const TextStyle(color: Colors.grey),
textStyle: const TextStyle(fontSize: 14),
),
onChanged: (c) => ...,
)
Key parameters #
| Parameter | Description |
|---|---|
items |
The List<T> of choices. |
onChanged |
Called with the selected T. |
value |
The currently selected item. |
itemLabel |
Maps an item to its label (defaults to toString()). |
groupBy |
Groups items under headers. |
isItemEnabled |
Disables specific items. |
enableSearch |
Shows a search box. |
searchMatcher |
Custom search predicate. |
showSelectAll |
Multi-select: show "select all" / "clear" actions. |
direction |
top, bottom, left, right, or auto. |
fieldStyle |
Styling for the input field (colors, border, font, padding). |
menuStyle |
Styling for the menu box and rows (colors, border, fonts, size). |
searchStyle |
Styling for the search bar (fill, border, hint, text, icon). |
itemBuilder |
Fully custom item rows. |
enabled |
Enables/disables the whole dropdown. |
Roadmap #
- ✅ Single-select: search, grouping, positioning, custom colors, disable
- ✅ Multi-select (with optional select-all/clear)
- ✅ Async loading
See implementasi_plan.md for the full plan.
License #
MIT