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.
- Basic Usage
- Constructor
- Props
- Types
- Date Range Selection
- Min/Max Constraints
- Custom Display Format
- Event Handling
- State Variants
- Styling Examples
- Calendar Internals
- All Supported Classes
- Customizing Theme
- Related Documentation
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,
DatePickerMode mode = DatePickerMode.single,
DateTime? value,
DateRange? range,
ValueChanged? onChanged,
ValueChanged? onRangeChanged,
DateTime? minDate,
DateTime? maxDate,
String? className,
String placeholder = 'Select date',
bool disabled = false,
Set states = const {},
DateDisplayFormat? displayFormat,
})
Props
| Prop | Type | Default | Description |
|---|---|---|---|
mode |
DatePickerMode |
single |
Selection mode: single or range |
value |
DateTime? |
null |
Currently selected date (single mode) |
range |
DateRange? |
null |
Currently selected range (range mode) |
onChanged |
ValueChanged |
null |
Callback fired on date selection (single mode) |
onRangeChanged |
ValueChanged |
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 |
const {} |
Custom states for dynamic styling |
displayFormat |
DateDisplayFormat? |
null |
Custom function to format dates for display |
Types
DatePickerMode
Determines if the picker operates in single date or date range selection mode.
enum DatePickerMode {
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: DatePickerMode.range enables two-click range selection with hover preview.
DateRange? _dateRange;
WDatePicker(
mode: DatePickerMode.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',
)
[!NOTE] Constraints are compared at day-level granularity. Time components are stripped before comparison.
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: DatePickerMode.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
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 |
[!NOTE] The calendar chrome (header, grid, day cells) uses hardcoded Wind classes and is not configurable via
className. TheclassNameprop only controls the trigger element.
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