Skip to main contentSkip to docs navigation

Chassis Calendar is a standalone date grid — the same calendar DatePicker renders in its popover, usable on its own.

Overview

Calendar is a date grid with keyboard-navigable cells, usable on its own wherever an inline calendar is needed — no popover, no text field. DatePicker renders this same component inside its popover, so the two always look and behave identically.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'

export const Example = () => {
  return <Calendar aria-label="Event date" />
}

Min & max dates

Restrict the selectable range with minValue/maxValue. Dates outside the range are shown but disabled.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'
import { today, getLocalTimeZone } from '@internationalized/date'

export const Example = () => {
  const now = today(getLocalTimeZone())

  return <Calendar aria-label="Appointment date" maxValue={now.add({ days: 30 })} minValue={now} />
}

Unavailable dates

Pass isDateUnavailable to mark specific dates as unselectable without excluding them from the grid — useful for booked dates, holidays, or (as below, using @internationalized/date's locale-aware isWeekend) weekends. Unavailable dates remain focusable so screen reader users can discover why they can't be picked.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'
import { isWeekend } from '@internationalized/date'

export const Example = () => {
  return (
    <Calendar
      aria-label="Appointment date"
      isDateUnavailable={(date) => isWeekend(date, 'en-US')}
    />
  )
}

From a list of dates

For data-driven cases — booked dates fetched from an API, say — pass unavailableDates instead: a plain array of ISO 8601 (YYYY-MM-DD) strings. It composes with isDateUnavailable when both are given; a date unavailable by either is unavailable.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'
import { today, getLocalTimeZone } from '@internationalized/date'

export const Example = () => {
  const now = today(getLocalTimeZone())
  const unavailableDates = [now.add({ days: 2 }), now.add({ days: 5 }), now.add({ days: 9 })].map(
    (date) => date.toString()
  )

  return <Calendar aria-label="Appointment date" unavailableDates={unavailableDates} />
}

Weekend styling

Saturday/Sunday (or whichever days the active locale considers the weekend — determined via @internationalized/date's isWeekend) are shown muted automatically, no prop required. This is purely visual — weekends stay selectable unless also marked unavailable. Every example on this page shows it. See Internationalization for how the active locale is set.

Calendar shows month and year <select>s next to the prev/next buttons, for jumping further than one page at a time.

First day of week

The week starts on Monday by default, regardless of the active locale. Pass firstDayOfWeek to override it — 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', or 'sat'.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'

export const Example = () => {
  return <Calendar aria-label="Event date" firstDayOfWeek="sun" />
}

Multiple dates

Pass selectionMode="multiple" to select any number of independent dates instead of one — clicking a date toggles it on or off, and value/defaultValue/onChange switch from a single date to an array of dates. Unlike RangeCalendar, the selected dates don't need to be contiguous.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'
import { today, getLocalTimeZone } from '@internationalized/date'

export const Example = () => {
  const now = today(getLocalTimeZone())

  return (
    <Calendar
      aria-label="Event dates"
      defaultValue={[now, now.add({ days: 5 }), now.add({ days: 12 })]}
      selectionMode="multiple"
    />
  )
}

Multiple months

Pass visibleMonths to show more than one month side by side, sharing one selection — independent of selectionMode, so it works the same with a single selected date or several. Each month gets its own header with its own month/year dropdowns, kept in sync. The prev/next buttons move to a single global overlay instead of repeating in every month's header, paging the whole visible span at once.

October 2026
JSX
import { Calendar } from '@chassis-ui/react'
import { today, getLocalTimeZone } from '@internationalized/date'

export const Example = () => {
  const now = today(getLocalTimeZone())

  return <Calendar aria-label="Event date" defaultValue={now} visibleMonths={2} />
}

Styling

Like DatePicker, Calendar isn't styled by @chassis-ui/css — chassis-css's own datepicker styling is built entirely around Vanilla Calendar Pro's specific DOM output, which React Aria's calendar hooks don't produce. Calendar instead ships its own scoped CSS (built on @chassis-ui/tokens' public design tokens, so it still matches the current theme and light/dark mode) in @chassis-ui/react/style.css, which an app imports once (see Theming).

Accessibility

Calendar has no visible label of its own — pass aria-label or aria-labelledby any time it's used standalone (as in every example above). When composed inside DatePicker's popover, the accessible name instead comes from the picker's own label.

Scope

  • Contiguous ranges — Calendar selects independent dates or none at all; for a start/end range, see RangeCalendar.

API

Calendar

PropTypeDefault
aria-label?string—
An accessible label for the calendar, used when there's no visible label. Required for standalone use — a calendar grid has no other accessible name of its own.
aria-labelledby?string—
Identifies a visible label element for the calendar.
autoFocus?boolean—
Whether to automatically focus the calendar when it mounts.
className?string—
A string of all className you want applied to the base component.
defaultFocusedValue?DateValue | null—
The date the calendar first shows and focuses, in place of the selected date or today. DatePicker and DateRangePicker pass their placeholderValue here while empty.
defaultValue?DateValue | DateValue[] | null—
The initial selected date (uncontrolled). The initial selected dates (uncontrolled).
disabled?boolean—
Prevents the calendar from being focused or interacted with.
firstDayOfWeek?"sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"'mon'
The day that starts the week, overriding the default set by the active locale.
isDateUnavailable?((date: DateValue) => boolean)—
Callback that is called for each date in the calendar. If it returns true, that date is shown but cannot be selected.
labels?Partial<CalendarLabels>—
Overrides for the handful of strings this component renders itself rather than getting from the active locale (the year view's paging arrows, its live-region announcements, and the month/year header buttons). Everything else — month and weekday names, the calendar system — follows I18nProvider's locale via react-aria and needs no override. Merged over the English defaults, so passing one key leaves the rest alone.
maxValue?DateValue | null—
The maximum allowed date that a user may select.
minValue?DateValue | null—
The minimum allowed date that a user may select.
onChange?((value: DateValue) => void) | ((value: DateValue[]) => void)—
Callback fired when the selected date changes. Unlike DatePicker's onChange (whose segmented field can be cleared to null), a calendar selection is always a concrete date. Callback fired when the set of selected dates changes.
selectionMode?"single" | "multiple"'single' 'single'
Whether a single date or multiple, independently toggled dates can be selected.
unavailableDates?string[]—
ISO 8601 dates (YYYY-MM-DD) to mark unselectable, as a convenience alternative to isDateUnavailable for data-driven cases (e.g. booked dates fetched from an API). Composed with isDateUnavailable when both are given — a date unavailable by either is unavailable.
value?DateValue | DateValue[] | null—
The selected date (controlled). The selected dates (controlled).
visibleMonths?number1
Number of months to display side by side, sharing one selection. Wraps to multiple rows in a narrow container (e.g. a popover on a sm screen) rather than overflowing — the wrap is driven by the calendar's own width, not the viewport, so it adapts correctly regardless of where the calendar is embedded.