Date picker
A date field with a calendar that opens beside it. Its value is an ISO date, YYYY-MM-DD.
import '@usezeno/tokens/css';
import '@usezeno/core/web-components';
Examples
Basic
The control is a button that opens a calendar. value is always an ISO date, YYYY-MM-DD, whatever format is shown.
<zeno-date-picker label="Start date"></zeno-date-picker>
<zeno-date-picker label="Due date" value="2031-03-09"></zeno-date-picker>
<zeno-date-picker label="Date of birth" placeholder="Choose your date of birth"></zeno-date-picker>
Format and first day of the week
format builds the shown text from DD, MM and YYYY. first-day-of-week takes 0 for Sunday to 6 for Saturday.
<zeno-date-picker label="Due date" value="2031-03-09" format="DD.MM.YYYY" first-day-of-week="1"></zeno-date-picker>
<zeno-date-picker label="Due date" value="2031-03-09" format="YYYY-MM-DD"></zeno-date-picker>
Limits
Days before min, after max or listed in disabled-dates cannot be chosen. hide-today-button removes the “Today” button.
<zeno-date-picker
label="Delivery date"
value="2031-03-12"
min="2031-03-03"
max="2031-03-28"
disabled-dates='["2031-03-17", "2031-03-18"]'
hide-today-button
></zeno-date-picker>
Sizes and states
<zeno-date-picker size="sm" label="Small"></zeno-date-picker>
<zeno-date-picker size="lg" label="Large"></zeno-date-picker>
<zeno-date-picker label="Disabled" value="2031-03-09" disabled></zeno-date-picker>
<zeno-date-picker label="Read only" value="2031-03-09" readonly></zeno-date-picker>
<zeno-date-picker label="Due date" error="Choose a date after the start date."></zeno-date-picker>
In a form
The form gets the ISO date under name. A required picker with no date stops the submit and shows its message. change carries the value and a Date in event.detail.
<form>
<zeno-date-picker name="due" label="Due date" required></zeno-date-picker>
<zeno-button type="submit">Save changes</zeno-button>
<zeno-button type="reset" variant="ghost">Reset</zeno-button>
<p data-result>Nothing submitted yet.</p>
</form>
const form = document.querySelector('form');
const result = document.querySelector('[data-result]');
document.querySelector('zeno-date-picker').addEventListener('change', event => {
result.textContent = 'Chosen: ' + event.detail.value;
});
form.addEventListener('submit', event => {
event.preventDefault();
const sent = [...new FormData(form)].map(([name, value]) => name + '=' + value);
result.textContent = 'Submitted: ' + sent.join('&');
});
Attributes and properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
placeholder | placeholder | string | 'Select date' | |
format | format | string | 'MM/DD/YYYY' | How the chosen date is shown: any order of MM, DD and YYYY. |
min | min | string | The earliest date that can be chosen, as YYYY-MM-DD. | |
max | max | string | The latest date that can be chosen, as YYYY-MM-DD. | |
size | sizereflects | 'sm' | 'md' | 'lg' | 'md' | |
hideTodayButton | hide-today-button | boolean | false | |
disabledDates | disabled-dates | string[] | [] | Dates that cannot be chosen, each as YYYY-MM-DD. |
firstDayOfWeek | first-day-of-week | number | 0 | 0 is Sunday, 1 is Monday. |
valueAsDateread only | Date | The chosen date, or undefined when the value is empty or not a date. | ||
value | value | FormValue | '' | From FormFieldMixin. |
name | namereflects | string | From FormFieldMixin. | |
required | requiredreflects | boolean | false | From FormFieldMixin. |
disabled | disabledreflects | boolean | false | From FormFieldMixin. |
readonly | readonlyreflects | boolean | false | From FormFieldMixin. |
error | error | string | An error to show, for example one a server returned. From FormFieldMixin. | |
label | label | string | A visible label. An outside <label for> works as well. From FormFieldMixin. | |
hideRequiredIndicator | hide-required-indicator | boolean | false | From FormFieldMixin. |
touched | boolean | false | True once the user has left the control or a submit was refused. From FormFieldMixin. | |
dirty | boolean | false | True once the user has changed the value. From FormFieldMixin. | |
defaultValue | FormValue | '' | The value the control goes back to when its form is reset. From FormFieldMixin. | |
formread only | HTMLFormElement | null | From FormFieldMixin. | ||
labelsread only | NodeList | From FormFieldMixin. | ||
validityread only | ValidityState | From FormFieldMixin. | ||
validationMessageread only | string | From FormFieldMixin. | ||
willValidateread only | boolean | From FormFieldMixin. | ||
isDisabledread only | boolean | True when the element or an ancestor fieldset disables it. From FormFieldMixin. | ||
invalidread only | boolean | True when the control should look invalid. From FormFieldMixin. | ||
testId | string | Test ID for automated testing From ZenoElement. |
Methods
| Method | Description |
|---|---|
focus(options?: FocusOptions): void | Focus the component or its first focusable element From ZenoElement. |
open(): void | Opens the calendar and moves focus to the chosen day, or to today. |
close(): void | Closes the calendar. |
toggle(): void | Opens the calendar when it is closed and closes it when it is open. |
checkValidity(): boolean | Reports whether the value is valid without showing anything. Use reportValidity() or validate() to show the result as well. From FormFieldMixin. |
reportValidity(): boolean | From FormFieldMixin. |
setCustomValidity(message: string): void | From FormFieldMixin. |
validate(): boolean | Checks validity and shows the result on the control. From FormFieldMixin. |
blur(): void | Blur the component or its focused element From ZenoElement. |
Events
| Event | Detail | Description |
|---|---|---|
input | { value: FormValue; date: Date } | A date was picked. |
change | { value: FormValue; date: Date } | A date was picked. |
open | The calendar opened. | |
close | The calendar closed. |
Slots
This element has no slots.
CSS parts
| Part |
|---|
base |
calendar |
calendar-header |
day |
days |
error |
icon |
input |
label |
month-year |
today-button |
value |
weekday |
weekdays |
CSS custom properties
This element documents no custom properties of its own. Style it through its parts and through the tokens it reads.
The element's own source reads no tokens.
Types
type FormValue = string | number | boolean | Date | null | undefined;
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the date button. |
| EnterSpace | On the date button, opens the calendar and moves focus to the chosen day, or to today. Pressed again on the button, closes it. |
| ArrowDown | On the date button, opens the calendar. |
| ArrowLeftArrowRight | In the calendar, moves one day back or forward. |
| ArrowUpArrowDown | In the calendar, moves one week back or forward. |
| HomeEnd | Moves to the first or the last day of the week. |
| PageUpPageDown | Moves one month back or forward. |
| Shift+PageUpShift+PageDown | Moves one year back or forward. |
| EnterSpace | On a day, chooses it, closes the calendar and returns focus to the date button. |
| Tab | In the calendar, moves between the previous month button, the next month button, the day grid and “Today”. The grid is one tab stop. Leaving the picker closes the calendar. |
| Escape | Closes the calendar and returns focus to the date button. A dialog around the picker stays open. |
Forms
- With a
name, the element adds its value to the data of the form it is in, and is left out while it is disabled. - It is labelled by its
labelattribute or by a<label for>that points at itsid. - It takes part in validation:
checkValidity(),reportValidity()andsetCustomValidity()work, and an invalid element blocks the submit and matches:invalid. - It follows a
<fieldset disabled>around it and goes back to its first value when the form is reset. - The messages it writes itself, such as “Please fill out this field.”, are in English only.
Notes
- The control is a native
<button>witharia-haspopup="dialog",aria-expandedandaria-controls. Its name is the label and the shown date together, for example “Due date, 03/09/2031”. A<label for>outside the element also names it. - The calendar has
role="dialog"with the name “Choose a date” and opens in the top layer. A press outside it closes it. - Every day is a native
<button>named with the full date in the language of the page. Today hasaria-current="date", the chosen day hasaria-pressed="true", and days that cannot be chosen are disabled. - The month title is a polite live region, so a change of month is announced. Month and weekday names follow the closest
langattribute. - While the error text shows,
aria-describedbyon the date button points at it. Transitions are turned off when the user asks for reduced motion.
Tests and known gaps
- Tested in a real browser by
date-picker.test.ts. - Checked with axe-core in the light and the dark theme.
- Has no Storybook story.
30 of the 55 properties, methods, events, slots and parts above have no description in the source
- the property
placeholder - the property
size - the property
hideTodayButton - the property
value - the property
name - the property
required - the property
disabled - the property
readonly - the property
hideRequiredIndicator - the property
form - the property
labels - the property
validity - the property
validationMessage - the property
willValidate - the method
reportValidity() - the method
setCustomValidity() - the part
base - the part
calendar - the part
calendar-header - the part
day - the part
days - the part
error - the part
icon - the part
input - the part
label - the part
month-year - the part
today-button - the part
value - the part
weekday - the part
weekdays
Known gaps:
- The calendar icon in the button is missing. The picker asks for the icon
calendar, whichzeno-icondoes not register. - A date cannot be typed. The only way to enter one is to pick it in the calendar, which is slow for dates far away, such as a date of birth. There is no way to jump to a year except Shift+PageUp and Shift+PageDown.
- A chosen date cannot be cleared by the user. There is no clear button.
- The texts “Choose a date”, “Previous month”, “Next month” and “Today” and the validation messages are English and cannot be changed, while the month and day names follow the language of the page.
formatknows onlyDD,MMandYYYY, always as numbers with leading zeros. Month names and two-digit years are not possible, and the format does not follow the language of the page.- The date button does not say that it is required or invalid. The asterisk is hidden from assistive technology and the button has no
aria-requiredand noaria-invalid. - A
readonlypicker looks like a normal one and gives no sign that it is read only. Its button can still be pressed and keepsaria-haspopup, but nothing opens. - The calendar is a dialog that does not keep focus inside. Tab from its last button leaves the picker and closes the calendar.
- The arrow keys do not skip days that cannot be chosen. The position moves onto the disabled day, but focus cannot follow it, so each key press seems to do nothing until the position reaches a day that can be chosen again. Past
minormaxthat never happens. - The error text is not a live region. A screen reader reads it when focus is on the date button, but does not announce it when it appears.