Date Picker
Allows users to enter a date either by typing it directly or by selecting it from a calendar popover. The component combines text input flexibility with a visual calendar to simplify accurate date selection.
Usage
Use the Date Picker when users need to input a specific date while providing both manual entry and visual selection options. For scenarios requiring only simple date selection, a Calendar Inline alone may suffice.
Keyboard Handling
The user can use the following keyboard shortcuts in order to navigate trough the date picker:
Up/Down- Moves focus to the day above/below the current day.Right- Moves focus to the next day.Left- Moves focus to the previous day.Enter/Space- Shows and moves focus the calendar. If already shown, selects the focused day.Home- Selects the first day of the month.End- Selects the last day of the month.PageUp- Selects the same or closest day of the previous month.PageDown- Selects the same or closest day of the next month.Esc- Closes the date picker calendar.
API Reference
Component attribute(s)
x-h-date-picker
x-h-date-picker-trigger
x-h-date-picker-popupAttributes
x-h-date-picker
| Attribute | Values | Required | Description |
|---|---|---|---|
| data-size | smdefault | false | Changes the size of the date picker. |
x-h-date-picker-popup
| Attribute | Values | Required | Description |
|---|---|---|---|
| data-align | bottom-startbottombottom-endright-startrightright-endleft-startleftleft-endtop-starttoptop-end | false | Aligns the calendar popup relative to the date picker trigger. |
| data-aria-prev-year | string | false | Sets the aria-label attribute value for the previous year button. |
| data-aria-prev-month | string | false | Sets the aria-label attribute value for the previous month button. |
| data-aria-next-month | string | false | Sets the aria-label attribute value for the next month button. |
| data-aria-next-year | string | false | Sets the aria-label attribute value for the next year button. |
Modifiers
| Modifier | Description |
|---|---|
| table | Use when the input is inside a table |
Configuration
You can pass a configuration object to the popup as an expression or as a value.
| Key | Description |
|---|---|
| locale | The locale of the calendar as a BCP 47 language tag. If not provided, it is taken from the page's <html lang> attribute, then the browser locale. |
| firstDay | The start day of the week. 0 is Sunday. |
| min | The earliest date selectable. Must be provided in the standard ISO 8601 format - YYYY-MM-DD. |
| max | The latest date selectable. Must be provided in the standard ISO 8601 format - YYYY-MM-DD. |
| options | Intl.DateTimeFormat options. |
| delimiter | Custom separator character between day, month, and year in the display format (e.g. "-"). Does not affect the model value. |
| order | Custom display order of the date parts as a three-character string of Y (year), M (month), D (day) (e.g. "MDY" for month-day-year). Defaults to the locale's natural order. Does not affect the model value. |
| range | When true, the picker selects a start-and-end date range instead of a single date. See Range selection. |
| rangeSeparator | Text placed between the two dates in the display input when range is enabled. Defaults to " - ". |
Model
The date picker reads and writes dates as YYYY-MM-DD strings (e.g. "2025-06-09"), matching the value format of a native <input type="date">. The display format shown in the text input is separate and can be customised via the options key in the calendar config.
Events
| Event | Description |
|---|---|
| change | Fired on the inner input when the user picks a date from the calendar or types a valid date. The event bubbles, so a listener can be placed on the x-h-date-picker element. Read the new value from the bound model, as the input's own value holds the formatted display text instead. |
There is no need to use $watch to react to user selection - listen for change instead. See Listening for changes.
Display format
By default the input displays the date using the user's locale. To customise it, pass Intl.DateTimeFormat options via the options key on x-h-date-picker-popup. The model value always remains YYYY-MM-DD regardless of the display format.
<div x-h-date-picker ...>
<input type="text" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup="{ options: { day: '2-digit', month: '2-digit', year: 'numeric' } }" x-model="date"></div>
</div>Manual input typed by the user is parsed using the configured display format. For formats where the month appears as a word rather than a number, parsing falls back to the browser's native Date constructor.
Range selection
Set range: true on the popup config to let the user pick a start-and-end date range. The first click selects the start, the second completes the range (picks are ordered automatically, so clicking an earlier day second still produces a valid range). With the keyboard, press Enter once to set the start and again to set the end.
In range mode the model value is an object with start and end keys (each a YYYY-MM-DD string), and the input displays both dates joined by the rangeSeparator (default " - "):
{ start: '2025-06-09', end: '2025-06-16' }<div x-h-date-picker x-data="{ range: { start: '', end: '' } }">
<input type="text" id="date-input-range" />
<button x-h-date-picker-trigger aria-label="Choose date range"></button>
<div x-h-date-picker-popup="{ range: true }" x-model="range"></div>
</div>Validation timing
By default this control shows native-constraint errors (for example required) only after the user interacts with it or attempts to submit, not on page load. To validate on load instead, set data-validate="immediate" on a wrapping x-h-fieldset, x-h-field, or any ancestor element. Setting aria-invalid="true" yourself always shows the error immediately. See Fieldset for details.
Examples
<div
x-h-date-picker
x-data="{
date: '',
init() {
const d = new Date();
this.date = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
}"
>
<input type="text" id="date-input-1" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup x-model="date"></div>
</div>Listening for changes
<div x-h-date-picker x-data="{ date: '' }" @change="console.log('Selected date:', date)">
<input type="text" id="date-input-change" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup x-model="date"></div>
</div>With locale
<div
x-h-date-picker
x-data="{
date: '',
init() {
const d = new Date();
this.date = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
}"
>
<input type="text" id="date-input-locale" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup="{ locale: 'bg-BG', firstDay: 1 }" x-model="date"></div>
</div>With custom display format
<div
x-h-date-picker
x-data="{
date: '',
init() {
const d = new Date();
this.date = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
}"
>
<input type="text" id="date-input-2" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup="{ options: { day: '2-digit', month: 'long', year: 'numeric' }, order: 'DMY' }" x-model="date"></div>
</div>Invalid
Reacts to the native invalid state or to the aria-invalid attribute.
<div x-h-date-picker x-data="{ date: '2026-07-09' }">
<input type="text" id="date-input-invalid" aria-invalid="true" />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup x-model="date"></div>
</div>Disabled
Set the native disabled attribute on the inner input to disable the whole picker.
<div x-h-date-picker x-data="{ date: '2026-07-09' }">
<input type="text" id="date-input-disabled" disabled />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup x-model="date"></div>
</div>Read-only
Set the native readonly attribute on the inner input. The value is shown with a muted background, and neither typing nor the calendar popover can change it.
<div x-h-date-picker x-data="{ date: '2026-07-09' }">
<input type="text" id="date-input-readonly" readonly />
<button x-h-date-picker-trigger aria-label="Choose date"></button>
<div x-h-date-picker-popup x-model="date"></div>
</div>