Date Picker
How the Date Picker works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.
Each month is a WAI-ARIA grid with one tab stop: arrows move by a day or a week, Page Up and Page Down by a month, and every day is announced by its full date. The panel itself is a group. When it opens from a field, it belongs in a dialog that returns focus to that field.
Keyboard#
Moves between the month buttons, the grid (on the focused day), the presets and the footer buttons.
Moves to the next day, across the edge of the month. ← moves to the previous day.
Moves to the same day next week. ↑ moves to the same day last week.
Moves to the first day of the week. End moves to the last.
Moves to the same day in the previous month. Page Down moves to the next.
Moves to the same day in the previous year. With Page Down, the next year.
Chooses the focused day. Space does the same. In a range, the first choice sets the start and the second the end.
In a dialog, closes the picker without saving and returns focus to the field. This belongs to the dialog, not the panel.
Semantics#
role="grid"Labelled by the month heading. Weekday names are columnheaders with the full name (Monday) as their label.
Each monthrole="gridcell"A button with roving tabindex: only the focused day is in the tab order.
Each dayaria-labelThe full date, such as Tuesday, September 29, 2026, plus has events on a marked day.
Each dayaria-selectedOn the chosen day, or both ends of a range.
Chosen dayaria-current="date"So today is announced, not only ringed in blue.
Todayaria-disabledDays outside the allowed dates stay focusable so arrow keys can pass them, but cannot be chosen. Days from the next or previous month are hidden.
Unavailable daysaria-live="polite"A change of month is announced, and so is the range spelled out in the footer.
Month heading, Rangearia-pressedEach preset is a toggle button; the active one is pressed.
PresetsColour contrast#
Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.
Day numbers · Basic, Today, Range, Active
Plain days are #6d6d6d on white; today and in-range days are #005cf8, the range on a 10% blue band; the chosen day is white on #005cf8. All pass.
WCAG 1.4.3 Contrast (Minimum) ↗Weekday names · Weekday row
MON to SUN are drawn in #cbcbcb, 1.62:1 on white. They are real text, not disabled text, and fail.
WCAG 1.4.3 Contrast (Minimum) ↗Today and event marks · Today, Marked
Today's 1px ring and the 3px event dot are #005cf8, 5.4:1 on white. Both are also in the day's name, so colour is not the only signal.
WCAG 1.4.11 Non-text Contrast ↗WCAG 1.4.1 Use of Color ↗Range band · Range
The 10% blue band is too pale to read as a boundary on its own. The start and end are filled, aria-selected, and spelled out in the footer.
WCAG 1.4.1 Use of Color ↗Focus indicator · Focus visible
A 2px ring in #1a6ef8 off a white gap, on keyboard focus only. It measures 4.54:1 on white, so it clears 3:1 on every light surface the library uses.
WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗Disabled days · Disabled
Days outside the month or the allowed dates are #cbcbcb and exempt as inactive.
WCAG 1.4.3 Contrast (Minimum) ↗Measured contrast#
Text against what sits directly behind it on the white panel, from the ds.css tokens; the range band is 10% #005cf8 over white, and a hovered day turns its cell white; text is held to 4.5:1 and the arrow glyph to 3:1. 4 of 24 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.
Motion#
- Day cells, month buttons and footer buttons change colour over 120ms. All are turned off under prefers-reduced-motion.
- Changing month swaps the grid in place; nothing slides.
Checklist#
Open the picker from a field in a dialog: move focus to the chosen day (or today), trap Tab inside, close on Escape, and return focus to the field.
Let people type the date into the field as well. The grid is for browsing, not the only way in.
Day names come from the en-US locale. Match the page's language so they are read correctly.
The weekday row (#cbcbcb, 1.62:1) and the muted Range: label (#8f8f8f, 3.23:1) are below 4.5:1 and need darker shades before the code library ships.
Day cells are 40px and the month buttons 32px, both over the 24px minimum.
Say why a day is unavailable (fully booked, in the past) in text near the picker; a grey number alone does not explain it.
