Calendar

Date grid for selection.

Install the peer

Every date component needs @internationalized/date, which is a peerDependency — install it yourself.

Dates are DateValue objects, not JavaScript Dates. You construct them, so there has to be exactly one copy of that library — two copies means objects that stop lining up, and that is the kind of bug that takes an afternoon to find.

Usage

Root hands you months and weekdays through a snippet and you render the table. Bits owns everything hard: the roving grid, arrow keys across weeks and months, PageUp/PageDown, Home/End, and the calendar arithmetic itself.

value is the selection and placeholder is the month on screen — both bindable, and they are genuinely separate: paging through months must not change what is selected.

SMTWTFS
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

The 20th is marked unavailable — struck through, not merely greyed out.

Naming the calendar

Pass calendarLabel. Bits builds the root's accessible name as `${calendarLabel} ${month} ${year}` and defaults the first part to the literal word "Event" — so an unset calendar announces as "Event January 2026", which tells the user nothing about what they are picking.

Pass "Departure date", "Due date", "Booking". Verified in the rendered DOM and asserted in the tests, because the prop name does not make this obvious.

The nav buttons are the opposite case: Bits hardcodes their aria-label to "Previous" and "Next" and merges its own props last, so an aria-label you pass is discarded. Do not waste time trying.

Disabled vs unavailable

These are different answers and they are styled differently on purpose:

  • isDateDisabled / minValue / maxValueout of range. Faded out; there is nothing to reason about.
  • isDateUnavailableexists but taken. A fully booked day. Kept readable and struck through, which reads as "that date is gone" rather than "that date does not exist".

Collapsing both into one grey blur throws away information the user needs. data-today gets a ring rather than a fill, so today stays legible even when it is also the selected day.

Locale is not cosmetic

locale changes the month names, which day the week starts on, and the numeral system. Hardcoding Monday-first or Sunday-first is a correctness bug for half your users, not a styling preference. Pass the user's locale.

Add MonthSelect and YearSelect for any range wider than a few months. Paging one month at a time to reach a birth year is not navigation.

Props

Calendar.Root

PropTypeDefault
type required `single` selects one date, `multiple` selects several. Required, because it also decides the shape of `value`.'single' | 'multiple'
value Selected date, or dates in `multiple` mode. Bindable. A `DateValue` from `@internationalized/date`, not a JS `Date`.DateValue | DateValue[] | undefined
onValueChange Called when the selection changes.(value: DateValue | DateValue[] | undefined) => void
placeholder The month currently on screen. Bindable.DateValue
minValue Earliest selectable date.DateValue
maxValue Latest selectable date.DateValue
isDateUnavailable Return true for dates that exist but cannot be picked — a booked day.(date: DateValue) => boolean
isDateDisabled Return true for dates to disable outright.(date: DateValue) => boolean
numberOfMonths How many months to show at once.number1
locale BCP-47 locale driving month names, weekday order and numerals.string'en'
calendarLabel What this calendar is FOR. Bits builds the root's accessible name as `${calendarLabel} ${month} ${year}`, and its default is the literal word "Event" — so an unset calendar announces as "Event January 2026", which tells the user nothing. Pass "Departure date", "Booking", "Due date".string'Event'
disabled booleanfalse
readonly booleanfalse
monthFormat Show the month and year dropdowns in the header.'long' | 'short' | 'narrow' | 'numeric' | '2-digit'false
class Extra classes merged onto the root.string
children Snippet<[SnippetProps]>

Every other part takes class plus its native attributes. Grid, GridHead, GridBody, GridRow, HeadCell, Header, Heading, the nav buttons and the selects are shared with Range Calendar.