Skip to content

WDatePicker

View raw .md

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
Preview widgets/date_picker_basic The live demo is not available for this example. date_picker_basic.dart View source
dart
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.

dart
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:

dart
// 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#

dart
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.

dart
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.

dart
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:

dart
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.

Preview widgets/date_picker_range The live demo is not available for this example. date_picker_range.dart View source
dart
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:

  1. First click: Sets the range start. The onRangeChanged callback fires with a DateRange where end is null.
  2. Hover: As the user moves the mouse, dates between start and the hovered date are highlighted with a blue tint.
  3. 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.

dart
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:

dart
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:

dart
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):

dart
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
dart
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:

dart
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:

dart
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#

Preview widgets/date_picker_styled The live demo is not available for this example. date_picker_styled.dart View source
dart
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#

dart
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#

dart
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:

text
'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-primary text-white rounded-full
In range bg-primary-100 dark:bg-primary-900/30 text-primary-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 route through the theme primary token (bg-primary for the selected day, bg-primary-100 / text-primary-700 for the in-range fill). Override primary to recolor the calendar to your brand; the neutral grays remain configurable via their own palette keys. The default primary is aliased to the Tailwind blue swatch, so leaving it unchanged keeps the original blue look.

dart
WindTheme(
  data: WindThemeData(
    colors: {
      'primary': MaterialColor(0xFF16A34A, {
        100: Color(0xFFDCFCE7),  // Range fill
        500: Color(0xFF16A34A),  // Selected day (shade 500 == bg-primary)
        700: Color(0xFF15803D),  // Range text
        900: Color(0xFF14532D),  // Dark mode range
      }),
    },
  ),
  child: MyApp(),
)

The calendar only reads shades 100 / 500 / 700 / 900, so the partial swatch above is enough for it. Other primary-driven widgets read further shades (WSelect uses 400 / 600 / 700 for its raw icon colors); a missing shade safely falls back to the swatch's base color rather than throwing, but if you share one primary across widgets, prefer a complete 50-950 swatch (e.g. a generated MaterialColor) so every shade renders as intended.

esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.