Datepicker

A date, a range, a week, a month, a quarter or a year, picked from a panel or typed into the field.

Accessibility

A date picker is a text field with a calendar beside it. The field is accessible, and the calendar is only partly so. Plan around the field.

What it renders

The field is a plain <input>, marked aria-invalid when the typed text does not parse. The panel renders in a portal at the end of the page.
  • The field is not a combobox. It has no aria-expanded or aria-controls, so nothing announces that the panel opened.
  • The calendar is a table of clickable cells with no grid role. A cell's state (selected, today, disabled) is shown by style only.
  • The four header arrows are real buttons with labels from the locale, like “Previous month”, but they are left out of the tab order.
  • The clear button has a label from the locale. The suffix glyph is decorative and hidden from screen readers.
For assistive technology the field is the whole component, so make the field carry everything the reader needs.

Labeling it

The picker has no label of its own. With only a placeholder, a screen reader announces it as “edit, blank”.
  • Pass id and point a <label htmlFor> at it. The id reaches the inner input.
  • Inside a Form.Item with a label, this is done for you, even for a single field.
  • On a RangePicker, pass id as a pair: id={{ start: "from", end: "to" }}. A single string names one field and leaves the other unnamed.
  • A placeholder is not a label. Use it for the format (“YYYY-MM-DD”), since it disappears once there is a value.
  • State the limits in text. A reader who never opens the panel cannot find out that weekends are unavailable.

Keyboard: the field works, the panel does not

  • In the field. Typing works. Enter commits once the text parses. Escape closes the panel and restores the previous value. Tab commits what is there and moves on.
  • With a mask format. Under format={{ type: "mask" }}, Left and Right move between segments, Up and Down change the focused segment, and Backspace clears it.
  • In the panel. The cells, the header arrows, the presets and the Today link cannot be reached with Tab, and arrow keys do not move through the grid. The OK button is the only thing you can focus, and only when needConfirm is on.
A keyboard user picks a date by typing it, so everything the panel offers needs another route.
  • Do not make a preset the only way to a window. Offer the same shortcuts in a Segmented or a Select beside the field.
  • Do not set inputReadOnly, which removes typing. A function format and multiple both make the field read-only, so avoid them where keyboard entry matters.
  • Accept several format patterns so typing is forgiving, and set preserveInvalidOnBlur so a half-typed date is not thrown away on blur.

Announcing the date

A cell reads as its day number, and “14” is not a date. The full date is only in the cell's title, which screen readers do not reliably announce.
  • Repeat the committed value in full next to the field: “Checking in Saturday, March 14, 2026”.
  • For a range, give both ends and the length: “April 1 to 30, 30 nights”.
  • If that text changes as the reader picks, put it in a polite live region. Do not put one on the field itself.
  • The hover preview (previewValue, on by default) writes the hovered date into the field. It never happens for a keyboard user, and on touch it can leave a date showing that was never picked. Do not rely on it.

Disabled dates and range fields

  • A disabled date is gray and ignores clicks. It is not marked disabled for screen readers.
  • Prefer minDate and maxDate over disabledDate for a window. They also disable the header arrows, which are buttons and do announce it.
  • Whatever the rule, write it in help text. That is the only form a reader who cannot see the gray will get.
  • A RangePicker is two inputs and one panel. The active end is shown by an underline and by focus, so keep the focus ring and the underline visible.
  • Label the two ends differently (“From” and “To”), so tabbing from one to the other is heard.
  • When an end cannot be left empty (allowEmpty is false), pass aria-required too.

Contrast and motion

The panel shows four states in small cells, and three of them are color only. All four come from your theme, so check them when you change colorPrimary.
  • Today is a thin border in colorPrimary. A light primary makes it disappear.
  • The selected date is a filled cell. Check its text against the primary fill, not against the panel.
  • Dates inside a range are a light tint, often the weakest contrast on the page.
  • Disabled dates may be low contrast, so the rule behind them has to be written out as well.
  • The panel fades and slides in. Turn motion off with ThemeScope theme={{ token: { motion: false } }} for readers who ask for reduced motion, and put your own animations behind motion-safe:.