Skip to main content

Date picker

<zeno-date-picker>FormsForm controlSource

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.

Save changes Reset

Nothing submitted yet.

<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​

PropertyAttributeTypeDefaultDescription
placeholderplaceholderstring'Select date'
formatformatstring'MM/DD/YYYY'How the chosen date is shown: any order of MM, DD and YYYY.
minminstringThe earliest date that can be chosen, as YYYY-MM-DD.
maxmaxstringThe latest date that can be chosen, as YYYY-MM-DD.
sizesizereflects'sm' | 'md' | 'lg''md'
hideTodayButtonhide-today-buttonbooleanfalse
disabledDatesdisabled-datesstring[][]Dates that cannot be chosen, each as YYYY-MM-DD.
firstDayOfWeekfirst-day-of-weeknumber00 is Sunday, 1 is Monday.
valueAsDateread onlyDateThe chosen date, or undefined when the value is empty or not a date.
valuevalueFormValue'' From FormFieldMixin.
namenamereflectsstring From FormFieldMixin.
requiredrequiredreflectsbooleanfalse From FormFieldMixin.
disableddisabledreflectsbooleanfalse From FormFieldMixin.
readonlyreadonlyreflectsbooleanfalse From FormFieldMixin.
errorerrorstringAn error to show, for example one a server returned. From FormFieldMixin.
labellabelstringA visible label. An outside <label for> works as well. From FormFieldMixin.
hideRequiredIndicatorhide-required-indicatorbooleanfalse From FormFieldMixin.
touchedbooleanfalseTrue once the user has left the control or a submit was refused. From FormFieldMixin.
dirtybooleanfalseTrue once the user has changed the value. From FormFieldMixin.
defaultValueFormValue''The value the control goes back to when its form is reset. From FormFieldMixin.
formread onlyHTMLFormElement | null From FormFieldMixin.
labelsread onlyNodeList From FormFieldMixin.
validityread onlyValidityState From FormFieldMixin.
validationMessageread onlystring From FormFieldMixin.
willValidateread onlyboolean From FormFieldMixin.
isDisabledread onlybooleanTrue when the element or an ancestor fieldset disables it. From FormFieldMixin.
invalidread onlybooleanTrue when the control should look invalid. From FormFieldMixin.
testIdstringTest ID for automated testing From ZenoElement.

Methods​

MethodDescription
focus(options?: FocusOptions): voidFocus the component or its first focusable element From ZenoElement.
open(): voidOpens the calendar and moves focus to the chosen day, or to today.
close(): voidCloses the calendar.
toggle(): voidOpens the calendar when it is closed and closes it when it is open.
checkValidity(): booleanReports 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(): booleanChecks validity and shows the result on the control. From FormFieldMixin.
blur(): voidBlur the component or its focused element From ZenoElement.

Events​

EventDetailDescription
input{ value: FormValue; date: Date }A date was picked.
change{ value: FormValue; date: Date }A date was picked.
openThe calendar opened.
closeThe 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​

KeyWhat it does
TabMoves focus to the date button.
EnterSpaceOn the date button, opens the calendar and moves focus to the chosen day, or to today. Pressed again on the button, closes it.
ArrowDownOn the date button, opens the calendar.
ArrowLeftArrowRightIn the calendar, moves one day back or forward.
ArrowUpArrowDownIn the calendar, moves one week back or forward.
HomeEndMoves to the first or the last day of the week.
PageUpPageDownMoves one month back or forward.
Shift+PageUpShift+PageDownMoves one year back or forward.
EnterSpaceOn a day, chooses it, closes the calendar and returns focus to the date button.
TabIn 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.
EscapeCloses 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 label attribute or by a <label for> that points at its id.
  • It takes part in validation: checkValidity(), reportValidity() and setCustomValidity() 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> with aria-haspopup="dialog", aria-expanded and aria-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 has aria-current="date", the chosen day has aria-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 lang attribute.
  • While the error text shows, aria-describedby on 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, which zeno-icon does 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.
  • format knows only DD, MM and YYYY, 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-required and no aria-invalid.
  • A readonly picker looks like a normal one and gives no sign that it is read only. Its button can still be pressed and keeps aria-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 min or max that 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.