DatePicker
Popover date grid using the ARIA grid pattern.
import { DatePicker } from '@elirobinson/react/components/organisms/DatePicker';Styles: @elirobinson/react/styles/organisms/DatePicker.css — already included when you import @elirobinson/react/styles.css.
Show code
import { useState } from 'react';
import { DatePicker } from '@elirobinson/react/components/organisms/DatePicker';
export default function Basic() {
const [value, setValue] = useState<Date | undefined>(new Date(2026, 7, 15));
return (
<DatePicker label="Session date" value={value} onValueChange={setValue} className="demo-col" />
);
}When to use it
Use DatePicker when someone is choosing a single specific day — a season start date, a
session, a deadline — and seeing the calendar grid actually helps (today, the weekday, how far
out it is). For a date range, or heavy typed-date entry like a birthdate years in the past, a
plain text input with format validation is often faster: this picker has no keyboard grid
navigation and no month/year jump, so getting to a far-off date means clicking "Next month"
repeatedly.
Starting empty
value is optional — leave it undefined for a field with nothing selected yet. There's no
defaultValue: the displayed value always comes straight from the value prop, so
onValueChange has to actually update your state for a click to visibly select a day.
Show code
import { useState } from 'react';
import { DatePicker } from '@elirobinson/react/components/organisms/DatePicker';
export default function Empty() {
const [value, setValue] = useState<Date | undefined>();
return (
<DatePicker
label="First practice"
value={value}
onValueChange={setValue}
className="demo-col"
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | — | Accessible label for the trigger input (also its accessible name). |
onValueChangerequired | (date: Date) => void | — | — |
className | string | — | — |
value | Date | — | — |
Accessibility
- The calendar uses the WAI-ARIA grid pattern:
role="grid"(labeled with the visible month and year), onerole="row"per week, androle="gridcell"per day. Every row always has exactly 7 gridcells — leading/trailing blanks before the 1st and after the last day are real cells witharia-disabled="true"(notaria-hidden), specifically so the row stays structurally valid rather than short a cell. - The selected day's gridcell carries
aria-selected="true"(and only that one); today's date getsaria-current="date"on its button. These are independent — "selected" and "today" can be different days, or the same one. - Keyboard support is partial, and it's worth being precise about the gap: every day is a
real, individually focusable
<button>, soTab/Shift+Tabwalk through the visible month's days in order andEnter/Spaceactivate the focused one — but there's no roving tabindex and noArrowLeft/ArrowRight/ArrowUp/ArrowDowngrid navigation, and noEscapeto close the calendar. Plan keyboard testing around Tab and Enter/Space, not the full ARIA grid keyboard convention. - Clicking the (read-only) text input toggles the calendar open or closed. Clicking a day calls
onValueChangeand closes the calendar. Clicking anywhere outside the control closes it too. - "Previous month"/"Next month" have explicit
aria-labels and correctly roll over year boundaries in both directions. - The popover is positioned with plain CSS directly below the input — there's no viewport-collision handling, so give it room to render below the field.
Do
- Store the value from onValueChange in state you control — the field is fully controlled, with no internal fallback.
- Give it room below the input — the popover always opens downward with no flip-to-fit logic.
- Use it for a single, nearby date where seeing the calendar actually helps.
- Pair it with a real label — it becomes the trigger input’s accessible name.
Don't
- Expect Escape to close the calendar, or arrow keys to move between days — neither is implemented.
- Use it for a date far from today (a birth year, say) — there’s no month/year jump, only Prev/Next.
- Type directly into the field — it’s read-only by design; selection happens through the grid.
- Reach for it when you need a date range — this picker is single-date only.