Date Picker
A date picker component with range and presets.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
DatePicker is a <Calendar> hidden in a popover, with a trigger button that shows the selected date. The value is written to an <input type="hidden" name="…"> so it is posted with the rest of the form. The server handler needs no custom JS: it is a real form name/value pair.
Form contract:
- Single date:
name="dueDate"+value="2026-06-13"(ISO). A single hidden input; the POST givesdueDate=2026-06-13. - Range:
mode="range"+value(start) +valueEnd(end). Two hidden inputs,nameandnameEnd(default:{name}_end). The POST givesdueDate=…&dueDate_end=…. - required: sets
requiredon the hidden input; native HTML5 validation fires if it is empty on submit.
Client-side behavior (ui.js island):
- Click on the trigger → toggles the popover (
data-ui="toggle-pop"). - Click on a day → sets the hidden input, updates the trigger label, then closes the popover (single) or waits for the second date (range).
- Click on prev/next or the month/year dropdowns → rebuilds the calendar for the new month without a reload (see Calendar).
Useful options: placeholder (text when empty), locale="fr"|"en", weekStart (0 Sunday, 1 Monday by default), minDate/maxDate (ISO bounds), triggerWidth (button width, default w-[240px]).
Installation
$ bext ui add date-picker
Usage
Anatomy
<div data-ui="date-picker" data-name="…" data-mode="single|range">
├── <input type="hidden" name="…" value="yyyy-mm-dd" />
├── <input type="hidden" name="…_end" value="…" /> // range only
├── <button data-ui="toggle-pop"> // the trigger
│ ├── <CalendarIcon /> // left icon
│ └── <span>{value ? formatFR(value) : placeholder}</span>
└── <div id="dp-…" data-overlay hidden> // the popover
└── <Calendar value valueEnd locale weekStart … /> // the gridAPI Reference
DatePicker
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | — | Name of the hidden input. This is the name that appears in the form POST. |
| value | string | — | ISO value "yyyy-mm-dd". The hidden input receives this value. |
| valueEnd | string | — | End of the range (mode="range"). The nameEnd hidden input receives this value. |
| nameEnd | string | {name}_end | Name of the second hidden input, for the range end. |
| mode | "single" | "range" | "single" | "single": a single click closes the popover. "range": the first click sets value, the second sets valueEnd. |
| placeholder | string | "Choisir une date" (fr) / "Pick a date" (en) | Trigger text when empty. |
| required | boolean | — | Sets the required attribute on the hidden input → native HTML5 validation on submit. |
| disabled | boolean | — | Disables the trigger. |
| id | string | dp-${name} | id of the popover panel. Used as the data-target of the toggle-pop. |
| locale | "fr" | "en" | "fr" | Locale for the placeholder, the inner Calendar and the display format (dd/mm/yyyy vs yyyy-mm-dd). |
| weekStart | 0 | 1 | 1 | See Calendar.weekStart. |
| minDate | string | — | Lower ISO bound passed to the inner Calendar. |
| maxDate | string | — | Upper ISO bound passed to the inner Calendar. |
| triggerWidth | string (utility) | "w-[240px]" | Tailwind utility class for the width of the trigger button. |
| className | string | — | CSS classes on the root wrapper <div data-ui="date-picker">. |
Data Attributes
Attributes you can target in custom CSS to override the style for each state.
| Attribute | On | Values | Description |
|---|---|---|---|
| data-ui="date-picker" | root | — | Root marker. |
| data-name | root | string | Reflects the name prop. |
| data-mode | root | "single" | "range" | Read by the island to decide what a day click does (close vs wait for the second date). |
| data-locale | root | "fr" | "en" | Used by the island for the display format after selection. |
| data-ui-dp-value | hidden input | — | Marker: the island writes the ISO value here on day click. |
| data-ui-dp-end | hidden input | — | Same for valueEnd (range mode). |
| data-ui-dp-label | trigger span | — | The island updates the textContent with the formatted date after a click. |
Keyboard
| Key | Action |
|---|---|
| Tab | Focuses the trigger. Further Tab presses move through the Calendar. |
| Enter / Space | On a closed trigger: opens the popover. On a day button: selects it. |
| Escape | Closes the open popover. |
Accessibility
The trigger is a real <button type="button">, focusable and keyboard-activatable. The popover is a <div data-overlay hidden>: the island makes it visible on click, and closes it on an outside click or Escape.
The value is written to an <input type="hidden" name="…">, which is a real form field. The browser includes the hidden input in the POST automatically; on the server it is a plain form.get("dueDate"). HTML5 validation (required) works natively.
The display format after selection follows the locale: 13/06/2026 (fr) or 2026-06-13 (en). The ISO value sent to the server stays normalized.
For the trigger's aria-label: if there is no enclosing <label for="…">, add aria-label="Due date" or similar so the screen reader announces the purpose of the field.
Examples
Empty (placeholder)
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
<DatePicker name="dueDate" placeholder="Pick a date" />
French locale
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
<DatePicker name="dueDate" value="2026-06-13" locale="fr" />
Range
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Required (HTML5 validation)
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
<DatePicker name="dueDate" required placeholder="Required" />
minDate / maxDate bounds
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Wide trigger
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
<DatePicker name="dueDate" value="2026-06-13" triggerWidth="w-[360px]" />
Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.
On This Page