Persona

Calendar

Inputs · 1 prop · Updated Sep 22, 2026

Calendar renders one month as a table of day buttons. It is the grid DatePicker opens in a popover, documented separately because you can place it inline, and it carries almost none of its own API: one PDS prop, and react-aria’s calendar underneath it.

Calendar has no field, so a date gets picked out of the grid or not at all, and the grid is a fixed 266px table that does not reflow. The question is whether a month of squares fits the layout you have: an inline birthday or effective-date field, yes; a filter bar or a table cell, no.

Where the reader should be able to type the date as well as pick it, the component is DatePicker, which is this grid in a popover behind a date field. DatePicker exists in PDS and is not documented here yet. For two dates and the span between them, there is Range Calendar.

Days that fall outside the visible month are not drawn at all. The cell is rendered hidden rather than grayed, so a month can begin with empty squares and there is no option to show the neighboring month’s dates in them.

Calendar draws no marker on today. The cell styles selected, disabled and invalid dates, and nothing else, so today reads as today only while it is the focused cell. defaultFocusedValue puts the keyboard there and leaves value empty, which means the reader still has to press it.

Keep it that way. Today is a fact about the calendar and the selection is an answer, so a prefilled date records an answer nobody gave. On a date of birth field that answer is wrong every day of the year but one.

minValue and maxValue push everything past the bound into the disabled state, which the cell renders as a lighter number with nothing else changed. isDateUnavailable gets less than that: the PDS cell reads isDisabled and isInvalid and never reads isUnavailable, so a date ruled out one at a time looks exactly like a date you can choose, stays focusable, and then refuses the press. React-aria’s own API asks the consumer for a visual affordance there, and PDS does not draw one.

So the grid cannot explain itself, and the reader who finds out from the grid alone is the one who has already clicked. Put the constraint in text beside the calendar, and name it rather than gesturing at it: “Weekends are unavailable”, not “some dates are unavailable”.

Arrow keys move by a day, and up and down by a week

Section titled “Arrow keys move by a day, and up and down by a week”

The month is a role="grid" table and Tab reaches it once. Inside it, left and right move by a day, up and down move by a week, and moving past the edge of the month brings the next one into view. The two navigation buttons are named by react-aria in the reader’s locale, so there is no aria-label for you to set on them.

useCalendarBase writes the new value into a live region, and announces the new month when Previous or Next is pressed from outside the grid. That is worth knowing mainly because it is losable: a month grid assembled by hand out of buttons tells a sighted reader the selection changed and tells nobody else.

This is the ARIA grid pattern as react-aria implements it. Whether the PDS build honors all of it has not been verified here, and this paragraph should be replaced by someone who has checked.

Spell the month out wherever you display the date

Section titled “Spell the month out wherever you display the date”

The grid itself is formatted for the reader’s locale, so the rule is about everywhere the chosen date is shown afterwards: the field, the summary line, and any table it ends up in. 03/04 is the fourth of March to one reader and the third of April to another, and nothing around it settles which. Mar 4, 2025 is one date to both.

Wording for the field and its helper text is on Writing.

calendarRef is the only prop Calendar declares. The value, the change handler, the bounds and which dates are unavailable all come from AriaCalendarProps<DateValue>, which Calendar hands to useCalendarState untouched, so the table below is one row and the API is roughly twenty. The locale is not among them: Calendar reads it from react-aria’s context and overrides anything passed in.

NameType
calendarRef

Ref<HTMLDivElement>

Also accepts every prop of AriaCalendarProps<DateValue>.

Was this page helpful?