flutter_bottom_sheet_pickers 0.1.2 copy "flutter_bottom_sheet_pickers: ^0.1.2" to clipboard
flutter_bottom_sheet_pickers: ^0.1.2 copied to clipboard

Customizable Flutter bottom sheet pickers for single, multiple, searchable, lazy loaded, cascade, date, date range, and year-month selection.

flutter_bottom_sheet_pickers #

pub package pub points GitHub Issues GitHub Forks GitHub Stars GitHub License

Customizable Flutter bottom sheet pickers for single selection, multiple selection, searchable lists, paged lazy loading, up to three-level cascade selection, dates, date ranges, and year-month ranges.

Use it for form fields, filters, region pickers, store pickers, organization trees, and remote option lists that should feel native inside a Flutter bottom sheet. The package depends only on the Flutter SDK and exposes chainable builders, so most pickers can be opened with one short expression.

Features #

  • Single and multiple bottom sheet pickers
  • Searchable local option lists
  • Paged lazy loading for remote option lists
  • Single and multiple cascade pickers with up to three levels
  • Cascade options from CascadeOption, map-like lists, or adjacency maps
  • Disabled options, optional empty confirmation, and custom option rows
  • Optional checkboxes for multiple selection
  • Date, date range, year-month, and year-month range pickers
  • Calendar helper labels for Gregorian, lunar, Buddhist, Tibetan, Islamic, Yi, and Hebrew calendars
  • Month and year shortcut navigation that automatically respects the allowed date range
  • Built-in cancel, reset, and confirm actions
  • Configurable primary color and action button border radius
  • Built-in labels for English, simplified Chinese, traditional Chinese, Thai, Burmese, Brazilian Portuguese, Canadian French, Italian, and Spanish

Language: English | 中文

Platform Support #

Android iOS MacOS Web Linux Windows

Requirements #

  • Flutter >=3.13.0 <4.0.0
  • Dart >=3.1.0 <4.0.0

Installation #

Add the package to your pubspec.yaml:

dependencies:
  flutter_bottom_sheet_pickers: ^0.1.1

Import it:

import 'package:flutter_bottom_sheet_pickers/flutter_bottom_sheet_pickers.dart';

API overview #

Entry point Use case Return value
BottomSheetPickers.single<T>(context) Single selection, searchable single selection, lazy single selection Future<T?>
BottomSheetPickers.multiple<T>(context) Multiple selection, searchable multiple selection, lazy multiple selection Future<List<T>?>
BottomSheetPickers.cascade(context) Up to three-level cascade single selection Future<CascadeSelection?>
BottomSheetPickers.cascade(context).multiple() Up to three-level cascade multiple selection Future<List<CascadeSelection>?>
BottomSheetPickers.calendar(context) Date selection Future<DateTime?>
BottomSheetPickers.dateRange(context) Date range selection Future<DateTimeRange?>
BottomSheetPickers.yearMonth(context) Year-month or year-month range selection Future<YearMonth?> / Future<YearMonthRange?>
BottomSheetPickers.setLocalizations(...) App-wide picker labels void
BottomPickerConfig(...) Local picker labels for one widget subtree Widget

Getting started #

Single Picker #

final String? selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a fruit",
).options(
  ["Apple", "Orange", "Banana"],
  initialValue: "Apple",
).show();

Confirm on Option Tap #

final String? selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a fruit",
).options(
  ["Apple", "Orange", "Banana"],
).confirmOnTap()
    .show();

Custom Sheet Height #

final String? selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a fruit",
).height(360)
    .options(["Apple", "Orange", "Banana"])
    .show();

Multiple Picker #

final List<String>? selected = await BottomSheetPickers.multiple<String>(
  context,
  title: "Choose tags",
).options(
  ["New", "Popular", "Recommended"],
  initialValue: ["New"],
).show();

Multiple Picker with Checkboxes #

final List<String>? selected = await BottomSheetPickers.multiple<String>(
  context,
  title: "Choose tags",
).options(
  ["New", "Popular", "Recommended"],
  initialValue: ["New"],
).checkbox()
    .show();
final String? selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a city",
).options(cities)
    .searchSupported(placeholder: "Search city")
    .show();

Lazy Loading #

final String? selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a store",
).lazyLoad(
  parameters: {"country": "TH"},
  lazyRequestFuture: (params) async {
    final pageIndex = params["page_index"] as int;
    final pageSize = params["page_size"] as int;
    final keyword = params["keyword"] as String?;
    return loadStores(pageIndex: pageIndex, pageSize: pageSize, keyword: keyword);
  },
).show();

The lazy loader receives page_index, page_size, and keyword in the parameter map and should return the current page as List<T>. When search is enabled, keyword contains the active search text.

Cascade Picker #

final CascadeSelection? selected = await BottomSheetPickers.cascade(
  context,
  title: "Choose location",
).options(
  [
    CascadeOption(
      id: "province_a",
      label: "Province A",
      children: [
        CascadeOption(
          id: "city_a",
          label: "City A",
          children: [
            CascadeOption(id: "town_a", label: "Town A"),
          ],
        ),
      ],
    ),
  ],
).initialValue(CascadeSelection.byIds("province_a", "city_a", "town_a"))
    .cascadeAllItemSupported()
    .show();

Multiple Cascade Picker #

final List<CascadeSelection>? selected = await BottomSheetPickers.cascade(
  context,
  title: "Choose locations",
).options(options)
    .multiple()
    .initialValues([
      CascadeSelection.byIds("province_a", "city_a", "town_a"),
    ])
    .cascadeAllItemSupported(allItemLabel: "All")
    .show();

Map-like Cascade Data #

final options = [
  {
    "id": 1,
    "label": "Province A",
    "value": "province-a",
    "children": [
      {
        "id": 11,
        "label": "City A",
        "children": [
          {"id": 111, "label": "Town A"}
        ]
      }
    ]
  }
];

The parser reads the option label from label, value, name, then id.

You can also pass an adjacency map. Each key is a parent id, and its list contains the child nodes:

final adjacencyOptions = {
  null: [
    {"id": "province_a", "label": "Province A"}
  ],
  "province_a": [
    {"id": "city_a", "label": "City A"}
  ],
  "city_a": [
    {"id": "town_a", "label": "Town A"}
  ],
};

Use CascadeSelection.byIds(...) for initial values. The picker resolves those ids against the option tree and returns a CascadeSelection containing the matched CascadeOption objects.

Disabled options and empty confirmation #

final selected = await BottomSheetPickers.multiple<String>(
  context,
  title: "Choose tags",
).options(
  tags,
  disabledValues: ["Archived"],
).allowNoSelection()
    .show();

disabledValues are compared with the option value. For cascade pickers, pass the option ids or values that should be disabled. allowNoSelection() lets users confirm without selecting an item.

Date Picker #

final DateTime? selectedDate = await BottomSheetPickers.calendar(
  context,
  title: "Choose date",
  initialDate: DateTime(2026, 7, 7),
  firstDate: DateTime(2026, 1, 1),
  lastDate: DateTime(2026, 12, 31),
).show();

firstDate and lastDate are inclusive bounds. The previous month, next month, previous year, and next year buttons are shown only when the target month is inside those bounds.

Date Range Picker #

final DateTimeRange? range = await BottomSheetPickers.dateRange(
  context,
  title: "Choose period",
  initialDateRange: DateTimeRange(
    start: DateTime(2026, 7, 1),
    end: DateTime(2026, 7, 7),
  ),
  firstDate: DateTime(2026, 1, 1),
  lastDate: DateTime(2026, 12, 31),
).show();

Calendar Helper Labels #

final DateTime? selectedDate = await BottomSheetPickers.calendar(
  context,
  initialDate: DateTime(2026, 7, 7),
).calendarType(CalendarType.yi)
    .show();

The header and returned value remain Gregorian. When a CalendarType is specified, day cells show small helper labels for that calendar.

Year-Month Picker #

final YearMonth? selectedMonth = await BottomSheetPickers.yearMonth(
  context,
  title: "Choose month",
  initialYearMonth: const YearMonth(2026, 7),
  firstYearMonth: const YearMonth(2020, 1),
  lastYearMonth: const YearMonth(2030, 12),
).show();

Year-Month Range Picker #

final YearMonthRange? range = await BottomSheetPickers.yearMonth(
  context,
  title: "Choose month range",
  firstYearMonth: const YearMonth(2020, 1),
  lastYearMonth: const YearMonth(2030, 12),
  isRange: true,
).show();

When no initial range is provided, the start defaults to the current year-month and the end is empty until the user selects one. End candidates start from the selected start year-month.

Return Values #

  • Confirm in a single picker returns T?.
  • Confirm in a multiple picker returns List<T>?.
  • Confirm in a single cascade picker returns CascadeSelection?.
  • Confirm in a multiple cascade picker returns List<CascadeSelection>?.
  • Confirm in a date picker returns DateTime?.
  • Confirm in a date range picker returns DateTimeRange?.
  • Confirm in a year-month picker returns YearMonth?.
  • Confirm in a year-month range picker returns YearMonthRange?.
  • Cancel, tapping outside the sheet, and system back return null.
  • Reset returns an empty list for multiple pickers and a reset selection for single cascade pickers.

Theme #

BottomPickerTheme derives button background, button border, checked color, selected option background, and disabled button background from primaryColor.

final selected = await BottomSheetPickers.single<String>(
  context,
  title: "Choose a fruit",
  themeData: const BottomPickerTheme(
    primaryColor: Color(0xFF1677FF),
    buttonBorderRadius: BorderRadius.all(Radius.circular(12)),
  ),
).options(fruits).show();

Localization #

The package does not require a localization delegate. By default, labels are resolved from the current Flutter locale, then the platform locale, then English.

Built-in labels are available through:

BottomPickerLocalizations.en
BottomPickerLocalizations.zh
BottomPickerLocalizations.zhHant
BottomPickerLocalizations.th
BottomPickerLocalizations.my
BottomPickerLocalizations.ptBR
BottomPickerLocalizations.frCA
BottomPickerLocalizations.it
BottomPickerLocalizations.es

For app-level configuration:

BottomSheetPickers.setLocalizations(
  localizations: BottomPickerLocalizations.byLocale(currentLocale),
);

For apps that use their own localization extension:

BottomSheetPickers.setLocalizations(
  builder: (context) => BottomPickerLocalizations(
    cancel: context.i18n("cancel"),
    reset: context.i18n("reset"),
    confirm: context.i18n("confirm"),
  ),
);

Unset labels fall back to the built-in labels for the active locale.

Use BottomPickerConfig when only one subtree needs a local override:

BottomPickerConfig(
  localizations: BottomPickerLocalizations.zh,
  child: PageContent(),
)

Additional information #

  • The runnable example app lives in example/.
  • Before publishing, run flutter analyze, flutter test, and flutter pub publish --dry-run.
  • Feel free to file an issue if you have any problem or feature request.
1
likes
0
points
229
downloads

Publisher

unverified uploader

Weekly Downloads

Customizable Flutter bottom sheet pickers for single, multiple, searchable, lazy loaded, cascade, date, date range, and year-month selection.

Repository (GitHub)
View/report issues

Topics

#picker #bottom-sheet #cascade-picker #bottom-picker #selection

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_bottom_sheet_pickers