WDatePicker
A utility-first date picker component built on WPopover with support for single date selection, date range selection, min/max constraints, and custom display formatting.
On this page
widgets/date_picker_basic
The live demo is not available for this example.
date_picker_basic.dart
View source
WDatePicker(
value: _selectedDate,
onChanged: (date) => setState(() => _selectedDate = date),
className: 'w-full p-3 border border-gray-300 rounded-lg',
placeholder: 'Select a date',
)
Basic Usage#
WDatePicker renders a trigger container that opens a popover-based calendar on click. It handles month navigation, today highlighting, and date constraints out of the box.
DateTime? _selectedDate;
WDatePicker(
value: _selectedDate,
onChanged: (date) {
setState(() => _selectedDate = date);
},
className: 'bg-white border rounded-md px-4 py-2 hover:border-blue-500',
)
When no className is provided, the trigger uses this default styling:
// Default trigger className
'bg-white border border-gray-300 rounded-lg p-3 dark:bg-gray-800 dark:border-gray-600'
The calendar popover opens below the trigger and auto-flips if there isn't enough space. In single mode, the popover closes automatically after a date is selected.
Constructor#
const WDatePicker({
Key? key,
WDatePickerMode mode = WDatePickerMode.single,
DateTime? value,
DateRange? range,
ValueChanged<DateTime>? onChanged,
ValueChanged<DateRange>? onRangeChanged,
DateTime? minDate,
DateTime? maxDate,
String? className,
String placeholder = 'Select date',
bool disabled = false,
Set<String> states = const {},
DateDisplayFormat? displayFormat,
})
Props#
| Prop | Type | Default | Description |
|---|---|---|---|
mode |
WDatePickerMode |
single |
Selection mode: single or range |
value |
DateTime? |
null |
Currently selected date (single mode) |
range |
DateRange? |
null |
Currently selected range (range mode) |
onChanged |
ValueChanged<DateTime>? |
null |
Callback fired on date selection (single mode) |
onRangeChanged |
ValueChanged<DateRange>? |
null |
Callback fired on range selection (range mode) |
minDate |
DateTime? |
null |
Earliest selectable date |
maxDate |
DateTime? |
null |
Latest selectable date |
className |
String? |
null |
Wind utility classes for the trigger container |
placeholder |
String |
'Select date' |
Text shown when no value is selected |
disabled |
bool |
false |
Prevents interaction, shows forbidden cursor |
states |
Set<String> |
const {} |
Custom states for dynamic styling |
displayFormat |
DateDisplayFormat? |
null |
Custom function to format dates for display |
Types#
WDatePickerMode#
Determines if the picker operates in single date or date range selection mode.
enum WDatePickerMode {
single, // Pick a single date
range, // Pick a start and end date
}
DateRange#
Represents a date range with a required start and optional end date.
class DateRange {
final DateTime start;
final DateTime? end;
bool get isComplete => end != null;
DateRange copyWith({DateTime? start, DateTime? end});
}
DateDisplayFormat#
A typedef for custom date formatting:
typedef DateDisplayFormat = String Function(DateTime date);
When no displayFormat is provided, dates display as "Jan 15, 2025" format.
Date Range Selection#
Setting mode: WDatePickerMode.range enables two-click range selection with hover preview.
widgets/date_picker_range
The live demo is not available for this example.
date_picker_range.dart
View source
DateRange? _dateRange;
WDatePicker(
mode: WDatePickerMode.range,
range: _dateRange,
onRangeChanged: (range) => setState(() => _dateRange = range),
placeholder: 'Check-in / Check-out',
className: 'w-64 border p-3 rounded-lg',
)
How range selection works:
- First click — Sets the range start. The
onRangeChangedcallback fires with aDateRangewhereendisnull. - Hover — As the user moves the mouse, dates between start and the hovered date are highlighted with a blue tint.
- Second click — Completes the range. If the second date is before the first, they're automatically swapped. The popover closes.
The trigger display text updates throughout: "Jan 15, 2025 - ..." while in progress, then "Jan 15, 2025 - Jan 20, 2025" when complete.
Min/Max Constraints#
Use minDate and maxDate to restrict which dates are selectable. Dates outside the range appear dimmed and don't respond to clicks.
WDatePicker(
value: _selectedDate,
onChanged: (date) => setState(() => _selectedDate = date),
minDate: DateTime.now(),
maxDate: DateTime.now().add(const Duration(days: 90)),
className: 'p-3 border rounded-lg',
placeholder: 'Next 90 days only',
)
Custom Display Format#
Override the default "Jan 15, 2025" format with a custom function:
WDatePicker(
value: _selectedDate,
onChanged: (date) => setState(() => _selectedDate = date),
displayFormat: (date) => '${date.day}/${date.month}/${date.year}',
className: 'p-3 border rounded-lg',
)
In range mode, the format function applies to both start and end dates independently.
Event Handling#
Single Mode#
onChanged fires with a normalized DateTime (midnight, no time component) when the user selects a date:
WDatePicker(
value: _date,
onChanged: (date) {
setState(() => _date = date);
_loadSchedule(date);
},
)
Range Mode#
onRangeChanged fires twice during a range selection — once on the first click (start only) and once on the second click (complete range):
WDatePicker(
mode: WDatePickerMode.range,
range: _range,
onRangeChanged: (range) {
setState(() => _range = range);
if (range.isComplete) {
_calculateDuration(range.start, range.end!);
}
},
)
State Variants#
WDatePicker automatically manages several interactive states. Use state prefixes in className to apply conditional styles.
| State | Activated When |
|---|---|
hover: |
Mouse hovers over the trigger |
focus: |
Calendar popover is open |
open: |
Calendar popover is open (alias for focus:) |
disabled: |
disabled prop is true |
selected: |
A date or range has been selected |
WDatePicker(
value: _date,
onChanged: (date) => setState(() => _date = date),
className: 'p-3 border border-gray-300 rounded-lg '
'hover:border-blue-400 '
'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 '
'selected:bg-blue-50 '
'disabled:opacity-50 disabled:bg-gray-100',
)
Disabled State#
When disabled: true, the trigger shows a forbidden cursor and the popover won't open:
WDatePicker(
disabled: true,
value: DateTime(2025, 6, 15),
className: 'p-3 border rounded-lg disabled:opacity-50 disabled:bg-gray-100',
)
Styling Examples#
Default Styling#
Without a className, the trigger uses built-in defaults with dark mode support:
WDatePicker(
value: _date,
onChanged: (date) => setState(() => _date = date),
// Uses: 'bg-white border border-gray-300 rounded-lg p-3
// dark:bg-gray-800 dark:border-gray-600'
)
Interactive with Ring Focus#
widgets/date_picker_styled
The live demo is not available for this example.
date_picker_styled.dart
View source
WDatePicker(
value: _date,
onChanged: (date) => setState(() => _date = date),
className: 'w-full p-3 bg-white dark:bg-gray-800 '
'border border-gray-300 dark:border-gray-600 rounded-lg '
'hover:border-blue-400 dark:hover:border-blue-500 '
'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 '
'dark:focus:ring-blue-800 '
'selected:border-blue-500',
)
Compact Inline#
WDatePicker(
value: _date,
onChanged: (date) => setState(() => _date = date),
className: 'px-2 py-1 text-sm border rounded bg-gray-50 hover:bg-white',
placeholder: 'Date',
)
Borderless with Shadow#
WDatePicker(
value: _date,
onChanged: (date) => setState(() => _date = date),
className: 'p-3 bg-white rounded-xl shadow-md hover:shadow-lg',
)
Calendar Internals#
The calendar popover is styled with a fixed-width container:
'w-[320px] bg-white dark:bg-gray-800 border border-gray-200
dark:border-gray-700 rounded-xl shadow-xl p-4'
The calendar grid consists of:
| Component | Details |
|---|---|
| Header | Month/year label with left/right navigation arrows |
| Weekday row | Mo Tu We Th Fr Sa Su (Monday start) |
| Date grid | 6 rows × 7 columns = 42 cells |
| Today | Highlighted with bg-gray-100 dark:bg-gray-700 rounded-full |
| Selected | bg-blue-500 text-white rounded-full |
| In range | bg-blue-100 dark:bg-blue-900/30 text-blue-700 |
| Out of month | text-gray-300 dark:text-gray-600 |
| Disabled | text-gray-300 dark:text-gray-600, no click |
All Supported Classes#
Trigger (className)#
The className prop styles the trigger container. All Wind utility classes are supported:
| Category | Examples |
|---|---|
| Background | bg-white, bg-gray-50, dark:bg-gray-800 |
| Border | border, border-2, border-gray-300, rounded-lg, rounded-xl |
| Padding | p-3, px-4, py-2 |
| Sizing | w-full, w-64, w-[300px] |
| Ring | ring-2, ring-blue-200, ring-offset-2 |
| Shadow | shadow-sm, shadow-md, shadow-lg |
| Typography | text-sm (affects placeholder/display text indirectly via icon color) |
| Opacity | opacity-50, opacity-75 |
| State prefixes | hover:, focus:, open:, disabled:, selected:, dark: |
| Responsive | sm:, md:, lg:, xl:, 2xl: |
What className Does NOT Control#
The calendar popover, header, weekday labels, and day cells use internal Wind classes that are not configurable via props.
Customizing Theme#
The calendar's selection colors (bg-blue-500, bg-blue-100) and neutral grays use Tailwind color scales from WindThemeData. Override them to change the visual appearance:
WindTheme(
data: WindThemeData(
colors: {
...WindThemeData.defaultColors,
'blue': {
100: Color(0xFFDBEAFE), // Range fill
500: Color(0xFF3B82F6), // Selected day
700: Color(0xFF1D4ED8), // Range text
900: Color(0xFF1E3A8A), // Dark mode range
},
},
),
child: MyApp(),
)
Related Documentation#
- WFormDatePicker - Form-integrated date picker with validation
- WSelect - Dropdown selection component
- WPopover - The underlying overlay engine
- WInput - Standard text input widget