Date picker
A text field you can type a date into, with a calendar button for when you’d rather pick. Typing follows the page’s language; the calendar works from the keyboard.
- CSS
css/components/datepicker.css- JS
js/components/datepicker.js
01 — Example
Date taken
Dates before 2020 and after today are struck through and can’t be picked.
For example, 10/5/2026
02 — Behavior
Script
| Name | Detail |
|---|---|
| Typing | Follows the page’s language: lang="en" is month/day/year, lang="en-GB" day/month/year. ISO dates always work. |
| Calendar keys | Arrows move a day or a week, Home and End the week’s ends, Page Up and Page Down a month (with Shift, a year), Enter picks, Esc closes. |
| Options | data-min, data-max (a date or today), data-week-start, data-value. |
| Value | The hidden input submits YYYY-MM-DD. |
jewel:datechange | Detail { date, value }. API: el.jewelDatepicker.value, .open(), .close(). |
03 — Usage
Use it for
- Dates people may not know by heart, or need in a range.
Avoid
- Birthdays and other dates people know. Let them type.
- Date ranges. It picks one date.
04 — Accessibility
Notes
- The calendar is a grid people move through with the arrow keys; each day is read in full (“Monday 5 October 2026”).
- An unreadable or out-of-range date shows as a field error.
05 — Without Jewel’s JS
What the script does
The script reads typed dates, builds the calendar, moves through it from the keyboard and writes the ISO date into the hidden input. The CSS reads only the markup below, so a framework can render the calendar from its own state and leave out datepicker.js and data-component.
| State | Set this | Notes |
|---|---|---|
| Calendar | <div class="datepicker__panel" role="dialog" aria-label="Choose date"> inside .datepicker | Inside it: .datepicker__head (.datepicker__nav--prev, .datepicker__month, .datepicker__nav--next), a table.datepicker__grid of .datepicker__day buttons, and .datepicker__foot. |
| Open | Remove hidden from the panel; aria-expanded="true" on .datepicker__toggle | The toggle turns accent. It also carries aria-haspopup="dialog" and aria-controls. |
| Days | On .datepicker__day: aria-pressed="true" (chosen), aria-current="date" (today), disabled (out of range), .is-outside (another month) | Give the focused day tabindex="0" and the rest tabindex="-1". |
| Placement | data-align="end" or data-side="top" on .datepicker | CSS puts the panel under the field. The script lifts it into the top layer instead (popover, .is-floating, fixed top and left), so a content panel can’t clip it. |
| Value and errors | The ISO date (YYYY-MM-DD) in the hidden input; setCustomValidity() on .datepicker__input for a date that can’t be read or is out of range | Show it as a field error (see Text field). |
Events and keyboard
- The toggle, or Alt+↓ in the field, opens the calendar with focus on the chosen day, or today.
- In the grid, ← → move a day, ↑ ↓ a week, Home and End to the week’s ends, Page Up and Page Down a month (with Shift, a year). Move focus and
tabindex="0"together. - Picking a day, Today, or Esc closes it and returns focus to the toggle. A press outside, or focus leaving, closes it without moving focus. Clear closes it and focuses the field.
- The script reads typed dates in the page language’s order (
lang="en"month/day/year,lang="en-GB"day/month/year) or ISO, and tidies them on blur.jewel:datechangedoesn’t fire without it.