Date Picker
A controlled single-date picker composed from Remix Button, Popover and Calendar.
RemixDatePicker opens a Calendar from a real button.
It composes existing Remix controls; the application still owns the selected
date. Use Calendar directly when dates should remain
visible inline.
Basic usage
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
class AppointmentDatePicker extends StatefulWidget {
const AppointmentDatePicker({super.key});
@override
State<AppointmentDatePicker> createState() => _AppointmentDatePickerState();
}
class _AppointmentDatePickerState extends State<AppointmentDatePicker> {
DateTime? selected;
@override
Widget build(BuildContext context) => RemixDatePicker(
value: selected,
firstDate: DateTime(2026),
lastDate: DateTime(2028, 12, 31),
semanticLabel: 'Appointment date',
placeholder: 'Select a date',
isDateUnavailable: (date) => date.weekday == DateTime.sunday,
onChanged: (date) => setState(() => selected = date),
);
}An Overlay ancestor is required. A routed WidgetsApp supplies one; use
Overlay.wrap in a builder-only host. No Material theme or
MaterialLocalizations is required.
Ownership and interaction
value, inclusive bounds, unavailable dates and civil-date comparison follow the Calendar contract.enabled: falseor a nullonChangeddisables the trigger. Disabling an open picker closes it without selecting.- Opening focuses the calendar's selected/current, bounded date. Arrows browse without selecting. Enter, Space, click or tap requests one available date and closes the picker, including reselecting the existing date.
- The picker closes after a selection request even when the parent rejects it. There is no optimistic selected value or Apply/Cancel draft state.
onOpenfires when the popover opens andonClosewhen it has closed. A completed selection callsonChangedbeforeonClose; a dismissal calls onlyonClose, so the two are distinguishable. Removing an open picker does not callonClose, matching Select.- Escape and outside interaction dismiss without changing the value. Ordinary
dismissal restores the original live trigger. Replacing a borrowed
focusNodewhile open closes the picker; the application manages focus on its replacement. A month/year selector handles its own first Escape before the containing picker or dialog. - The popup is not a modal dialog. It uses the existing popover focus and dismissal behavior, without claiming modal background semantics.
- The panel constrains and scrolls in small spaces. Touch uses the same explicit controls; hover, swiping and a software keyboard are not required.
- Scrolling an ancestor closes a root picker without selecting. A picker nested in another menu or popover closes when the outer root closes.
- A view-size change, such as a rotation or window resize, closes the picker without selecting; reopen it in the new space. Keyboard and safe-area changes re-fit the open panel instead, so the keyboard hiding as the trigger takes focus from a text field does not dismiss it. Nested month/year menus inherit the panel's inset protection.
Clear the value by setting it to null in the parent. This button-only picker does not include a text parser, segmented field or time input. For an inclusive date range, use Date Range Picker.
Labels and presentation
Localize semanticLabel, placeholder, and calendarLabels. Supply
calendarLabels.noDate with the other localized labels for an empty selection.
semanticLabel is the field purpose and the trigger's accessible name. The
trigger's accessible value is the selected date from calendarLabels.fullDate,
or calendarLabels.noDate when empty; assistive technology joins the name and
value in its own locale. formatDate only changes the text painted on the
button (by default calendarLabels.fullDate), so a compact format is never
announced as the date. Set firstDayOfWeek explicitly for the application
locale. English defaults do not imply automatic locale formatting.
Style the composition with independent buttonStyle, popoverStyle,
calendarStyle, navigationStyle and selectorStyle values. Each child resolves
its own interaction states. There is no separate DatePicker styler, raw spec,
controller, or callable widget shorthand. Preset recipes are a separate release
from this shared runtime component.
The trigger label defaults to one line with an ellipsis under a finite width;
its accessible value remains complete. icon supplies a decorative leading
icon, styled by buttonStyle. placeholderStyle merges over buttonStyle
only when value is null; expandedStyle merges last while the popover is
open, including while a nested selector is open. Interaction variants still
resolve in the button's own state. Use these fragments for muted empty text
and an open-field ring. Month/year panels use a 4-pixel side offset.
Vanilla recipe
In a Vanilla app, install date_picker and import your generated UI barrel:
remix add date_pickerimport 'package:flutter/widgets.dart';
import 'ui/ui.dart';
Widget example() => UiDatePicker(
value: null,
firstDate: DateTime(2026),
lastDate: DateTime(2028, 12, 31),
onChanged: (value) { /* Store the requested value in application state. */ },
);Ui is the application's chosen prefix. Wrap the host Navigator in UiTheme.
This hand-written composite wires the Vanilla outline Button, rounded and
elevated Popover, calendar, icon navigation and Select recipes. Override each
child with buttonStyle, popoverStyle, calendarStyle, navigationStyle
and selectorStyle; each override merges last. The panel is offset from the
trigger and sized for the calendar. Remix retains all opening, dismissal and
selection behavior.
Constructor
Constructor
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
RemixDatePicker date_pickerConstructor({
Key? key,
required DateTime? value,
required DateTime firstDate,
required DateTime lastDate,
ValueChanged<DateTime>? onChanged,
VoidCallback? onOpen,
VoidCallback? onClose,
DateTime? currentDate,
bool Function(DateTime)? isDateUnavailable,
int firstDayOfWeek = DateTime.sunday,
bool enabled = true,
String semanticLabel = 'Choose date',
String placeholder = 'Select date',
String Function(DateTime)? formatDate,
RemixCalendarLabels calendarLabels = const RemixCalendarLabels(),
FocusNode? focusNode,
bool autofocus = false,
OverlayPositionConfig positioning = const OverlayPositionConfig(
sideOffset: 4,
collisionPadding: EdgeInsets.all(8),
),
IconData? icon,
ButtonStyler placeholderStyle = const ButtonStyler.create(),
ButtonStyler expandedStyle = const ButtonStyler.create(),
ButtonStyler buttonStyle = const ButtonStyler.create(),
CalendarStyler calendarStyle = const CalendarStyler.create(),
PopoverStyler popoverStyle = const PopoverStyler.create(),
IconButtonStyler navigationStyle = const IconButtonStyler.create(),
SelectStyler selectorStyle = const SelectStyler.create(),
}) => throw UnimplementedError();Fortal widgets
In a Fortal app, remix add date_picker installs <Prefix>DatePicker through
the same UI barrel. It retains the Remix date, labels, focus and controlled
selection contract above. The hand-written facade merges caller child styles
last over the reviewed Fortal calendar, ghost navigation and Select recipes.
Day targets and chevrons scale with text. Today uses bold gray12 ink; range
drafts have a separate perimeter and a quieter previous selection. Nested
month/year panels have a bounded height and a persistent scroll thumb.
See Fortal for theme setup.
Properties
Widget Properties
value → DateTime?
Required. Parent-owned selected date, or null for an empty selection. Civil-date bounds follow the calendar contract.
firstDate / lastDate → DateTime
Required. Inclusive civil-date bounds; firstDate must be on or before lastDate.
onChanged → ValueChanged<DateTime>?
Requests one complete date and closes even if the parent rejects it. Null disables the picker.
onOpen / onClose → VoidCallback?
Opening and closure notifications. Selection calls onChanged before onClose; dismissal calls only onClose. Removal does not call onClose.
currentDate → DateTime?
Today and fallback focus date; defaults to now.
isDateUnavailable → bool Function(DateTime)?
Marks dates unavailable for selection.
firstDayOfWeek → int
First weekday column, Monday=1 through Sunday=7. Defaults to Sunday.
enabled → bool
Defaults to true. Disabling an open picker closes it without selecting.
semanticLabel → String
Accessible field purpose. Defaults to Choose date.
placeholder → String
Visible empty text. Defaults to Select date.
formatDate → String Function(DateTime)?
Visible selected text only; accessible values use the full localized calendar labels.
calendarLabels → RemixCalendarLabels
Calendar and accessible-value labels; English by default.
focusNode → FocusNode?
Caller-owned trigger focus node. Replacing it closes the picker.
autofocus → bool
Defaults to false. Focuses the trigger without opening the calendar.
positioning → OverlayPositionConfig
Placement, offsets and collision padding relative to the trigger. Defaults to a side offset of 4 and collision padding of 8. The panel avoids safe-area and keyboard exclusions.
icon → IconData?
Optional decorative leading icon; size and color follow buttonStyle.
placeholderStyle → ButtonStyler
Fragment merged over buttonStyle only when value is null.
expandedStyle → ButtonStyler
Fragment merged last while the popover is open, including nested selectors.
buttonStyle → ButtonStyler
Trigger styling, resolved in its own state.
calendarStyle → CalendarStyler
Calendar surface and day styling.
popoverStyle → PopoverStyler
Popup surface styling.
navigationStyle → IconButtonStyler
Previous/next button styling.
selectorStyle → SelectStyler
Month/year selector styling.
Style Methods
Each child uses its own styler and interaction states. There is no separate DatePicker styler or raw spec. See Calendar for calendar and day style methods.
HeroUI widgets
In a HeroUI app, remix add date_picker installs <Prefix>DatePicker
with the same controlled value, bounds, availability callbacks and labels as Remix.
The hand-written composition merges caller styles last for each independent part.
The recipes port HeroUI web 3.2.6 colors, typography and day geometry. Month/year Select menus replace the upstream year grid. Date pickers use a field-shaped button in place of segmented text entry. Day tiles and icons grow with text; range drafts take visual precedence over the previous committed range. Picker panels retain equal 12px insets. See Open Code for theme setup.