Components
Date Picker
BetaA calendar popover for selecting single dates. Used in holiday search widgets, booking forms, and departure date fields throughout the booking funnel.
Purpose
The DatePicker provides a visual calendar for date selection — more discoverable than a native <input type="date"> and consistent across browsers. It appears wherever a single date must be chosen:
- Search widgets — departure date panel in the holiday SearchBar
- Booking forms — specific departure date, flight date selection
- Availability checks — "Check availability from" fields
The calendar highlights today in Wow Yellow and selected dates in Site Teal, creating clear visual hierarchy. Keyboard and mouse users can navigate month-by-month. Disabled dates (via minDate / maxDate) are shown in grey and are not selectable.
Usage
Reach for DatePicker whenever the value being captured is a calendar date — it is far more discoverable and consistent across browsers than a native <input type="date">. For a fixed, exhaustive list of non-date choices (country, room type), use Input with as="select" instead.
DatePicker typically sits inside the holiday SearchBar's departure/return date panels, in booking forms further down the funnel, and in availability-check fields — anywhere a single specific date must be chosen.
Anatomy
- 1Trigger button — Full-width h-11 button with rounded-sm border. Shows selected date or placeholder. Calendar icon on the right.
- 2Label — Sits above the trigger. 14px medium weight. Required asterisk in danger colour when required=true.
- 3Month header — Displays "Month Year" with left/right chevron buttons to navigate. Centred between arrows.
- 4Day header row — Su Mo Tu We Th Fr Sa — 7 columns. 12px medium grey text.
- 5Day grid — 7-column grid. Each day is a button. Today = yellow background. Selected = teal background + white text. Disabled = grey text, not clickable.
- 6Calendar popover — Absolute-positioned 288px wide card with rounded-md border and shadow-kuoni-md. Appears below the trigger.
Variants
Default — open
The calendar popover is toggled by clicking the trigger button. Click on a date to select it and close the calendar.
With pre-selected date
Pass an ISO date string (YYYY-MM-DD) as defaultValue to show an initial selection. Use value for a controlled component where the parent manages state.
With min/max constraints
Use minDate and maxDate (ISO strings) to disable dates outside a valid window — for example, limiting departure dates to the next 18 months.
Error state
Please select a departure date to continue.
Disabled
States
Trigger states
| State | Appearance |
|---|---|
| Default | Grey border, placeholder text in grey-400 |
| Focused | Teal border + teal focus ring |
| Selected | Dark text showing formatted date (e.g. "15 Jul 2025") |
| Error | Danger border, error message below |
| Disabled | Grey-50 background, 60% opacity, cursor-not-allowed |
Calendar day states
| State | Appearance |
|---|---|
| Default | Kuoni dark text, transparent background |
| Hover | Teal-50 background |
| Today | Wow Yellow background, dark text, semibold |
| Selected | Teal background, white text, semibold |
| Disabled | Grey-300 text, not clickable |
Best practices
Always pair DatePicker with a clear label explaining what date is required — 'Departure date', 'Return date', 'Check-in date'.
ISO date in defaultValue and the label provides clear context.
Render DatePicker without a label or placeholder in a dense form where users must guess what it's for.
An unlabelled trigger gives no context about what date is being selected.
Set minDate to today (or the earliest valid date) to prevent users from selecting invalid past dates.
minDate prevents selecting dates in the past, reducing booking errors.
Leave minDate unset on departure date fields where past dates are never valid.
Allowing past dates forces users to notice and correct an error at a later validation step.
Use defaultValue (uncontrolled) when the field starts with a logical default based on prior selections.
defaultValue of '2025-08-10' shows the current selection immediately on mount.
Show an empty DatePicker next to a pre-filled destination — pre-fill related fields together when context allows.
Without a default the calendar is empty — force users to always open and pick.
Pair required= with a descriptive error prop for form validation contexts.
A required asterisk and clear error message guides users to fix the missing date.
Silently block form submission without surfacing a DatePicker error message.
Silent failure leaves users confused about why they cannot proceed.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | — | ID for the trigger button element. Auto-generated as "datepicker" if not provided. |
| label | string | — | Visible label above the trigger button. Strongly recommended for accessibility. |
| value | string | — | Controlled selected date in ISO format (YYYY-MM-DD). When provided, the parent controls the selection. |
| defaultValue | string | — | Initial selected date for uncontrolled usage. ISO format (YYYY-MM-DD). |
| onChange | (iso: string, date: Date) => void | — | Called when a date is selected. Receives ISO string and Date object. |
| minDate | string | — | Earliest selectable date in ISO format. Dates before this are disabled. |
| maxDate | string | — | Latest selectable date in ISO format. Dates after this are disabled. |
| disabled | boolean | false | Disables the entire picker. Trigger button gets bg-grey-50 and cursor-not-allowed. |
| required | boolean | false | Marks the field as required with a red asterisk and aria-required. |
| placeholder | string | 'Select a date' | Text shown in the trigger when no date is selected. |
| error | string | — | Validation error message shown below the trigger. Switches border to danger colour. |
| helpText | string | — | Help text shown below the trigger. Hidden when error is shown. |
| className | string | — | Additional Tailwind classes on the root container element. |
Accessibility
Trigger button
The trigger has aria-expanded={open} and aria-haspopup="dialog" to signal that activation opens a dialog. aria-required={required} and aria-invalid={error ? 'true' : undefined} connect to WCAG form requirements.
Calendar dialog
The popover has role="dialog" and aria-modal="true". Its label is aria-label="Choose a date".
Day buttons
Each day button has aria-label="14 July 2025" (full written date). Selected days have aria-pressed={true}. Disabled days have the native disabled attribute.
Keyboard interaction
| Key | Action |
|---|---|
Enter / Space | Open/close the calendar |
Escape | Close the calendar |
Tab | Navigate: trigger → prev month → next month → day grid buttons |
| Arrow keys | Navigate between day buttons within the grid |
Enter on a day | Select the day and close the calendar |
Date formatting
The displayed date uses en-GB locale: "14 Jul 2025". The aria-valuetext on the day buttons uses the full month name for unambiguous screen reader announcement.
Related components
- Input — use
as="select"for fixed non-date choices; DatePicker shares the same label/error/helpText pattern. - CounterInput — the neighbouring control in most search widgets, for traveller and room counts.