dropdown_custom 0.4.0 copy "dropdown_custom: ^0.4.0" to clipboard
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 #

CI

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 auto flipping when it would run off-screen.
  • Type-safe generics — items are a plain List<T>; no wrapper class required. onChanged returns your T, not dynamic.
  • 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

1
likes
160
points
196
downloads

Documentation

API reference

Publisher

verified publisheroktajianto.com

Weekly Downloads

A customizable, zero-dependency dropdown with search, grouping, per-item enable/disable, custom colors, and free positioning (top/bottom/left/right).

Repository (GitHub)
View/report issues

Topics

#dropdown #ui #widget #picker #select

License

MIT (license)

Dependencies

flutter

More

Packages that depend on dropdown_custom