date-picker
Form-associated calendar date picker with text entry, month navigation, keyboard support, and progressive enhancement over native input[type=date].
Overview
The <date-picker> component wraps a native <input type="date"> and replaces the inconsistent browser chrome with a combobox text input and calendar popup. Users can type a date directly or pick from the calendar grid. It supports min/max date ranges, disabled dates, locale-aware formatting, and participates in form submission via ElementInternals.
<form-field> <label for="event">Event date</label> <date-picker name="event"> <input type="date" id="event" value="2026-04-15"> </date-picker></form-field>
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
name |
string | — | Form field name for submission. |
data-disabled-dates |
string | — | Comma-separated ISO dates to disable. Optionally append :reason for visual variants (e.g. 2026-04-05:booked,2026-04-06). |
data-highlight-dates |
string | — | Comma-separated ISO dates to highlight. Optionally append :category for visual variants (e.g. 2026-12-25:holiday,2026-04-15:deadline). |
disabled |
boolean | false |
Disables the picker. |
required |
boolean | false |
Marks the field as required for form validation. |
Native input attributes
The following attributes are read from the child <input type="date">:
| Attribute | Effect |
|---|---|
value |
Initial date in YYYY-MM-DD format. |
min |
Earliest selectable date. |
max |
Latest selectable date. |
Date Constraints
Use min and max on the native input to restrict the date range. Use data-disabled-dates to exclude specific dates.
<date-picker name="appointment" data-disabled-dates="2026-04-05,2026-04-06"> <input type="date" min="2026-04-01" max="2026-04-30"></date-picker>
Disabled Date Styles
Append a :reason to any disabled date to get a distinct visual treatment. Dates without a reason use the default faded style. Three built-in reasons are provided:
| Reason | Visual Treatment | Use Case |
|---|---|---|
booked |
Strikethrough text | Reservations, appointments already taken |
unavailable |
Diagonal hatching | Out-of-service, maintenance, closed periods |
holiday |
Accent color, bold | Public holidays, special dates |
<date-picker name="hotel" data-disabled-dates="2026-04-07:booked,2026-04-08:booked,2026-04-14:unavailable,2026-04-21:holiday"> <input type="date" min="2026-04-01" max="2026-04-30"></date-picker>
Custom reasons are also supported. Any value is set as data-disabled-reason on the calendar button, so you can target it with CSS:
td button[data-disabled-reason="maintenance"] { opacity: 0.5; background: var(--color-warning-subtle);}
Highlighted Dates
Use data-highlight-dates to visually mark key dates that remain selectable. Append a :category to each date for distinct styling. Three built-in categories are provided:
| Category | Visual Treatment | Use Case |
|---|---|---|
holiday |
Warm accent background and text | Public holidays, observances |
deadline |
Red accent background and text | Due dates, cutoff dates |
event |
Blue accent background and text | Meetings, scheduled events |
Dates without a category get a subtle default highlight. All highlighted dates remain fully selectable.
<date-picker name="schedule" data-highlight-dates="2026-04-10:event,2026-04-15:deadline,2026-04-21:holiday"> <input type="date" min="2026-04-01" max="2026-04-30"></date-picker>
Custom categories are supported. Any value is set as data-highlight on the button:
td button[data-highlight="payday"] { background: oklch(from green l c h / 0.1); border-color: oklch(from green l c h / 0.3); font-weight: 600;}
Form Integration
The component is form-associated via ElementInternals and works inside <form-field> for validation messaging. The submitted value is always in ISO format (YYYY-MM-DD).
<form> <form-field> <label for="dob">Date of birth</label> <date-picker name="dob" required> <input type="date" id="dob" min="1920-01-01" max="2025-12-31"> </date-picker> <output class="error" for="dob" aria-live="polite"></output> </form-field> <button type="submit">Submit</button></form>
Text Entry
The date input accepts typed dates in the following formats. The calendar navigates live as you type.
| Format | Example |
|---|---|
| ISO date | 2026-04-15 or 2026/04/15 |
| Month name + day + year | April 15, 2026 or Apr 15 2026 |
| Day + month name + year | 15 Apr 2026 |
| Month + year (partial) | April 2026 — navigates calendar without selecting a day |
Locale-aware month names are also accepted. Ambiguous numeric formats like 4/15/2026 are intentionally rejected to avoid locale confusion.
Keyboard
Text input
| Key | Action |
|---|---|
| Enter | Parse typed text and select the date if valid. |
| Arrow Down | Open calendar and move focus into the date grid. |
| Escape | Close calendar and revert text to selected date. |
| Tab | Close calendar and move to next form field. |
Calendar grid
| Key | Action |
|---|---|
| Arrow Left / Arrow Right | Move focus one day. |
| Arrow Up / Arrow Down | Move focus one week. |
| Page Up / Page Down | Move focus one month. |
| Shift + Page Up / Shift + Page Down | Move focus one year. |
| Home / End | Move to first / last day of the week. |
| Enter / Space | Select the focused date. |
| Escape | Close calendar and return focus to text input. |
| Tab | Close calendar and move to next form field. |
Events
| Event | Detail | Description |
|---|---|---|
date-picker:change |
{ value, date } |
Fired when a date is selected. value is the ISO string, date is a Date object. |
date-picker:open |
— | Fired when the calendar opens. |
date-picker:close |
— | Fired when the calendar closes. |
Accessibility
The text input uses role="combobox" with aria-haspopup="dialog" and aria-expanded, following the W3C Date Picker Combobox pattern. The calendar popup uses role="dialog" with an accessible label. The date grid uses role="grid" with individual day buttons. Today is marked with aria-current="date", the selected date with aria-selected="true", and disabled dates with aria-disabled="true". The month/year header uses aria-live="polite" so screen readers announce navigation changes.
Localization
The trigger button formats dates using Intl.DateTimeFormat and the user’s locale. Month/year headers and first-day-of-week detection are also locale-aware. The submitted form value is always ISO-formatted regardless of display locale.
JavaScript API
| Property | Type | Description |
|---|---|---|
value |
string | Get or set the selected date as an ISO string (YYYY-MM-DD). |
min |
string | Get or set the earliest selectable date. Updates the calendar immediately. |
max |
string | Get or set the latest selectable date. Updates the calendar immediately. |
Recipe: Date Range (Linked Pickers)
Two standard <date-picker> elements can be linked for date range selection. When the start date is picked, it sets min on the end picker. When the end date is picked, it sets max on the start. No custom range component needed.
<form-field> <label for="check-in">Check-in</label> <date-picker id="check-in" name="check_in"> <input type="date" id="check-in-input" min="2026-04-01"> </date-picker></form-field> <form-field> <label for="check-out">Check-out</label> <date-picker id="check-out" name="check_out"> <input type="date" id="check-out-input" min="2026-04-01"> </date-picker></form-field>
const checkIn = document.getElementById('check-in');const checkOut = document.getElementById('check-out'); // Start date constrains the end pickercheckIn.addEventListener('date-picker:change', (e) => { checkOut.min = e.detail.value;}); // End date constrains the start pickercheckOut.addEventListener('date-picker:change', (e) => { checkIn.max = e.detail.value;});
The min and max properties update the calendar constraints in real time. Dates outside the allowed range are automatically greyed out in the grid.