Calendar
Composable date picker with month grid, navigation, and year picker support built on React Aria Calendar
Usage
import { Calendar } from '@lenso/ui';"use client";
/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";
export function Basic() { return ( <Calendar aria-label="Event date"> <Calendar.Header> <Calendar.Heading /> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
import {Calendar} from '@lenso/ui';
export default () => ( <Calendar aria-label="Event date"> <Calendar.Header> <Calendar.Heading /> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody> {(date) => <Calendar.Cell date={date} />} </Calendar.GridBody> </Calendar.Grid> </Calendar>)Examples
Disabled
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function Disabled() { return ( <div {...stylex.props(layout.stack)}> <Calendar isDisabled aria-label="Event date" defaultValue={today(getLocalTimeZone())}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Calendar is disabled</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Read Only
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function ReadOnly() { return ( <div {...stylex.props(layout.stack)}> <Calendar isReadOnly aria-label="Event date" defaultValue={today(getLocalTimeZone())}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Calendar is read-only</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Default Value
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";import { parseDate } from "@internationalized/date";import { CalendarHeader, CalendarGrid } from "./demo-parts";export function DefaultValue() { return ( <Calendar aria-label="Event date" defaultValue={parseDate("2025-02-14")}> <CalendarHeader /> <CalendarGrid /> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Year Picker
Calendar.YearPickerTrigger, Calendar.YearPickerGrid, and their body/cell subcomponents provide an integrated year navigation pattern.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";
export function YearPicker() { return ( <Calendar aria-label="Event date"> <Calendar.Header> <Calendar.YearPickerTrigger> <Calendar.YearPickerTriggerHeading /> <Calendar.YearPickerTriggerIndicator /> </Calendar.YearPickerTrigger> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> <Calendar.YearPickerGrid> <Calendar.YearPickerGridBody> {({ year }) => <Calendar.YearPickerCell year={year} />} </Calendar.YearPickerGridBody> </Calendar.YearPickerGrid> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Controlled
Use controlled value and focusedValue for external state coordination and custom shortcuts.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, ButtonGroup, Calendar } from "@lenso/ui";import { getLocalTimeZone, parseDate, startOfMonth, startOfWeek, today, type CalendarDate,} from "@internationalized/date";import { useState } from "react";import { useLocale } from "react-aria-components/I18nProvider";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function Controlled() { const [value, setValue] = useState<CalendarDate | null>(null); const [focusedDate, setFocusedDate] = useState<CalendarDate>(() => parseDate("2025-12-25")); const { locale } = useLocale(); const select = (date: CalendarDate) => { setValue(date); setFocusedDate(date); }; return ( <div {...stylex.props(layout.stack)}> <ButtonGroup fullWidth size="sm" variant="tertiary"> <Button onClick={() => select(today(getLocalTimeZone()))}>Today</Button> <Button onClick={() => select(startOfWeek(today(getLocalTimeZone()), locale))}>Week</Button> <Button onClick={() => select(startOfMonth(today(getLocalTimeZone())))}>Month</Button> </ButtonGroup> <Calendar aria-label="Event date" focusedValue={focusedDate} value={value} onChange={setValue} onFocusChange={setFocusedDate} > <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Selected date: {value ? value.toString() : "(none)"}</CalendarNote> <div {...stylex.props(layout.controls)}> <Button size="sm" variant="secondary" onClick={() => select(today(getLocalTimeZone()))}> Set Today </Button> <Button size="sm" variant="secondary" onClick={() => select(parseDate("2025-12-25"))}> Set Christmas </Button> <Button size="sm" variant="tertiary" onClick={() => setValue(null)}> Clear </Button> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Min and Max Dates
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function MinMaxDates() { const now = today(getLocalTimeZone()); const maxDate = now.add({ months: 3 }); return ( <div {...stylex.props(layout.stack)}> <Calendar aria-label="Appointment date" maxValue={maxDate} minValue={now}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Select a date between today and {maxDate.toString()}</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Unavailable Dates
Use isDateUnavailable to block dates such as weekends, holidays, or booked slots.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import { isWeekend } from "@internationalized/date";import { useLocale } from "react-aria-components/I18nProvider";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function UnavailableDates() { const { locale } = useLocale(); return ( <div {...stylex.props(layout.stack)}> <Calendar aria-label="Appointment date" isDateUnavailable={(date) => isWeekend(date, locale)}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Weekends are unavailable</CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Weeks in Month
Set weeksInMonth to a fixed value (for example, 6) to keep the grid height stable when navigating between months. Use with care in non-Gregorian locales, similar to firstDayOfWeek.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function WeeksInMonth() { return ( <div {...stylex.props(layout.stack)}> <Calendar aria-label="Event date" weeksInMonth={6}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote> Always shows 6 weeks per month to avoid layout shift when navigating </CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Week View
Set visibleDuration={{ weeks: n }} to show one or more weeks at a time. Navigation advances by the visible week range. Use pageBehavior="single" to move one week at a time when showing multiple weeks.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Select. */import { Calendar } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, DurationSelect, layout } from "./demo-parts";export function WeekView() { const [weeks, setWeeks] = useState(1); return ( <div {...stylex.props(layout.controlsStack)}> <DurationSelect unit="weeks" value={weeks} onChange={setWeeks} /> <Calendar aria-label="Week view" visibleDuration={{ weeks }}> <CalendarHeader /> <CalendarGrid /> </Calendar> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Day View
Set visibleDuration={{ days: n }} to show a rolling window of consecutive days. Navigation advances by the visible day range. Use pageBehavior="single" to move one day at a time when showing multiple days.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Select. */import { Calendar } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, DurationSelect, layout } from "./demo-parts";export function DayView() { const [days, setDays] = useState(5); return ( <div {...stylex.props(layout.controlsStack)}> <DurationSelect unit="days" value={days} onChange={setDays} /> <Calendar aria-label="Day view" visibleDuration={{ days }}> <CalendarHeader /> <CalendarGrid /> </Calendar> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Multiple Selection
Set selectionMode="multiple" to let users select several dates. value, defaultValue, and onChange use an array of dates.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import type { DateValue } from "@internationalized/date";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarNote } from "./demo-parts";
const styles = stylex.create({ root: { display: "flex", flexDirection: "column", alignItems: "center", gap: 16 }, description: { textAlign: "center" },});
export function MultipleSelection() { const [value, setValue] = useState<readonly DateValue[]>([]); return ( <div {...stylex.props(styles.root)}> <Calendar aria-label="Event dates" selectionMode="multiple" value={value} onChange={setValue}> <Calendar.Header> <Calendar.Heading /> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> </Calendar> <CalendarNote> {value.length ? `${value.length} date(s) selected` : "Select multiple dates"} </CalendarNote> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Focused Value
Programmatically control which date is focused using focusedValue and onFocusChange.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, Calendar } from "@lenso/ui";import { parseDate, type DateValue } from "@internationalized/date";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, CalendarNote, layout } from "./demo-parts";export function FocusedValue() { const [focusedDate, setFocusedDate] = useState<DateValue>(() => parseDate("2025-06-15")); return ( <div {...stylex.props(layout.stack)}> <Calendar aria-label="Event date" focusedValue={focusedDate} onFocusChange={setFocusedDate}> <CalendarHeader /> <CalendarGrid /> </Calendar> <CalendarNote>Focused: {focusedDate.toString()}</CalendarNote> <div {...stylex.props(layout.controls)}> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-01-01"))} > Go to Jan </Button> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-06-15"))} > Go to Jun </Button> <Button size="sm" variant="secondary" onClick={() => setFocusedDate(parseDate("2025-12-25"))} > Go to Christmas </Button> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Cell Indicators
You can customize Calendar.Cell children and use Calendar.CellIndicator to display metadata like events.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";import { getLocalTimeZone, isToday } from "@internationalized/date";import { CalendarHeader, CalendarGrid } from "./demo-parts";const datesWithEvents = [3, 7, 12, 15, 21, 28];export function WithIndicators() { return ( <Calendar aria-label="Event date"> <CalendarHeader /> <CalendarGrid cell={(date) => ( <Calendar.Cell date={date}> {({ formattedDate }) => ( <> {formattedDate} {(isToday(date, getLocalTimeZone()) || datesWithEvents.includes(date.day)) && ( <Calendar.CellIndicator /> )} </> )} </Calendar.Cell> )} /> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Custom Navigation Icons
Pass children to Calendar.NavButton to replace the default chevron icons.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";import { CalendarGrid } from "./demo-parts";export function CustomIcons() { return ( <Calendar aria-label="Event date"> <Calendar.Header> <Calendar.Heading /> <Calendar.NavButton slot="previous"> <svg height={24} viewBox="0 0 24 24" width={24} xmlns="http://www.w3.org/2000/svg"> <path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6l6 6z" fill="currentColor" /> </svg> </Calendar.NavButton> <Calendar.NavButton slot="next"> <svg height={24} viewBox="0 0 24 24" width={24} xmlns="http://www.w3.org/2000/svg"> <path d="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6z" fill="currentColor" /> </svg> </Calendar.NavButton> </Calendar.Header> <CalendarGrid /> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Multiple Months
Render multiple grids with visibleDuration and offset for booking and planning experiences.
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ root: { containerType: "normal", width: "100%", maxWidth: "none", overflowX: "auto" }, months: { marginInline: "auto", display: "flex", width: "max-content", gap: 32 }, month: { width: 256 }, heading: { flex: "none" }, spacer: { width: 24, height: 24 },});
export function MultipleMonths() { return ( <Calendar aria-label="Event date" xstyle={styles.root} visibleDuration={{ months: 2 }}> <div {...stylex.props(styles.months)}> <div {...stylex.props(styles.month)}> <Calendar.Header> <Calendar.NavButton slot="previous" /> <Calendar.Heading xstyle={styles.heading} /> <div {...stylex.props(styles.spacer)} /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> </div> <div {...stylex.props(styles.month)}> <Calendar.Header> <div {...stylex.props(styles.spacer)} /> <Calendar.Heading xstyle={styles.heading} offset={{ months: 1 }} /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid offset={{ months: 1 }}> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> </div> </div> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Real-World Example
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX and native Base UI Button. */import { Button, Calendar } from "@lenso/ui";import { getLocalTimeZone, isWeekend, today, type CalendarDate } from "@internationalized/date";import { useState } from "react";import { useLocale } from "react-aria-components/I18nProvider";import * as stylex from "@stylexjs/stylex";import { CalendarHeader, CalendarGrid, layout } from "./demo-parts";const bookedDates = [5, 6, 12, 13, 14, 20];export function BookingCalendar() { const [selectedDate, setSelectedDate] = useState<CalendarDate | null>(null); const { locale } = useLocale(); return ( <div {...stylex.props(layout.stack)}> <Calendar aria-label="Booking date" isDateUnavailable={(date) => isWeekend(date, locale) || bookedDates.includes(date.day)} minValue={today(getLocalTimeZone())} value={selectedDate} onChange={setSelectedDate} > <CalendarHeader /> <CalendarGrid cell={(date) => ( <Calendar.Cell date={date}> {({ formattedDate, isUnavailable }) => ( <> {formattedDate} {!isUnavailable && !isWeekend(date, locale) && bookedDates.includes(date.day) && ( <Calendar.CellIndicator /> )} </> )} </Calendar.Cell> )} /> </Calendar> <div {...stylex.props(layout.bookingDetails)}> <div {...stylex.props(layout.legend)}> <span {...stylex.props(layout.legendItem)}> <span {...stylex.props(layout.mutedDot)} /> Has bookings </span> <span {...stylex.props(layout.legendItem)}> <span {...stylex.props(layout.defaultDot)} /> Weekend/Unavailable </span> </div> {selectedDate ? ( <Button size="sm" variant="primary"> Book {selectedDate.toString()} </Button> ) : null} </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
International Calendars
By default, Calendar displays dates using the calendar system for the user's locale. You can override this by wrapping your Calendar with I18nProvider and setting the Unicode calendar locale extension.
The example below shows the Indian calendar system:
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. */import { Calendar } from "@lenso/ui";import { getLocalTimeZone, today } from "@internationalized/date";import { I18nProvider } from "react-aria-components/I18nProvider";
export function InternationalCalendar() { return ( <I18nProvider locale="hi-IN-u-ca-indian"> <Calendar aria-label="Event date" defaultValue={today(getLocalTimeZone())}> <Calendar.Header> <Calendar.YearPickerTrigger> <Calendar.YearPickerTriggerHeading /> <Calendar.YearPickerTriggerIndicator /> </Calendar.YearPickerTrigger> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody>{(date) => <Calendar.Cell date={date} />}</Calendar.GridBody> </Calendar.Grid> <Calendar.YearPickerGrid> <Calendar.YearPickerGridBody> {({ year }) => <Calendar.YearPickerCell year={year} />} </Calendar.YearPickerGridBody> </Calendar.YearPickerGrid> </Calendar> </I18nProvider> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Note: The onChange event always returns a date in the same calendar system as the value or defaultValue (Gregorian if no value is provided), regardless of the displayed locale. This ensures your application logic works consistently with a single calendar system while still displaying dates in the user's preferred format.
Customization
Tailwind CSS
"use client";/** HeroUI v3.2.6 derived work. Copyright NextUI Inc. Apache-2.0. Modified for StyleX. */import { Calendar } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ root: { width: 252, borderRadius: "var(--radius-2xl)", borderWidth: 1, borderStyle: "solid", borderColor: { default: "color-mix(in oklab,var(--border) 80%,transparent)", ':is([data-theme="dark"] *)': "color-mix(in oklab,var(--border) 90%,transparent)", }, backgroundColor: "var(--surface)", padding: 12, boxShadow: { default: "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 1px color-mix(in oklab,var(--accent) 5%,transparent)", ':is([data-theme="dark"] *)': "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 1px color-mix(in oklab,var(--accent) 10%,transparent)", }, }, heading: { color: "var(--foreground)" }, cell: { backgroundColor: { default: null, ":hover": "var(--default)", ":is([data-hovered])": "var(--default)", ":is([data-selected])": "var(--accent)", ":is([data-selected]):is(:hover,[data-hovered])": "var(--accent-hover)", ":is([data-selected][data-outside-month])": "var(--default)", ":is([data-today])": "var(--accent-soft)", ":is([data-today]):is(:hover,[data-hovered])": "var(--accent-soft-hover)", ":is([data-selected][data-today])": "var(--accent)", ":is([data-selected][data-today]):hover": "var(--accent-hover)", }, color: { default: null, ":is([data-outside-month])": "var(--muted)", ":is([data-selected])": "var(--accent-foreground)", ":is([data-today])": "var(--accent-soft-foreground)", ":is([data-selected][data-today])": "var(--accent-foreground)", }, },});
export function CustomStyles() { return ( <Calendar aria-label="Custom styled calendar" xstyle={styles.root}> <Calendar.Header> <Calendar.Heading xstyle={styles.heading} /> <Calendar.NavButton slot="previous" /> <Calendar.NavButton slot="next" /> </Calendar.Header> <Calendar.Grid> <Calendar.GridHeader> {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>} </Calendar.GridHeader> <Calendar.GridBody> {(date) => <Calendar.Cell xstyle={styles.cell} date={date} />} </Calendar.GridBody> </Calendar.Grid> </Calendar> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
@layer components { .calendar { @apply w-72 rounded-2xl border border-border bg-surface p-3 shadow-sm; }
.calendar__heading { @apply text-sm font-semibold text-default-700; }
.calendar__cell[data-selected="true"] { @apply bg-accent text-accent-foreground; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
Calendar uses these classes in packages/styles/components/calendar.css and packages/styles/components/calendar-year-picker.css:
.calendar- Root container..calendar__header- Header row containing nav buttons and heading..calendar__heading- Current month label..calendar__nav-button- Previous/next navigation controls..calendar__grid- Main day grid..calendar__grid-header- Weekday header row wrapper..calendar__grid-body- Date rows wrapper..calendar__header-cell- Weekday header cell..calendar__cell- Interactive day cell..calendar__cell-indicator- Dot indicator inside a day cell..calendar-year-picker__trigger- Year picker toggle button..calendar-year-picker__trigger-heading- Heading text inside year picker trigger..calendar-year-picker__trigger-indicator- Indicator icon inside year picker trigger..calendar-year-picker__year-grid- Overlay grid of selectable years..calendar-year-picker__year-cell- Individual year option.
Interactive States
Calendar supports both pseudo-classes and React Aria data attributes:
- Selected:
[data-selected="true"] - Today:
[data-today="true"] - Unavailable:
[data-unavailable="true"] - Outside month:
[data-outside-month="true"] - Hovered:
:hoveror[data-hovered="true"] - Pressed:
:activeor[data-pressed="true"] - Focus visible:
:focus-visibleor[data-focus-visible="true"] - Disabled:
:disabledor[data-disabled="true"]
API Reference
Calendar
Calendar inherits all props from React Aria Calendar.
| Prop | Type | Default | Description |
|---|---|---|---|
selectionMode | 'single' | 'multiple' | 'single' | Whether one or many dates can be selected. |
value | DateValue | null or DateValue[] | null | - | Controlled selected date(s). Use an array when selectionMode is multiple. |
defaultValue | DateValue | null or DateValue[] | null | - | Initial selected date(s) (uncontrolled). |
onChange | (value: DateValue | null) or (value: DateValue[] | null) => void | - | Called when selection changes. |
focusedValue | DateValue | - | Controlled focused date. |
onFocusChange | (value: DateValue) => void | - | Called when focus moves to another date. |
minValue | DateValue | Calendar-aware 1900-01-01 | Earliest selectable date. |
maxValue | DateValue | Calendar-aware 2099-12-31 | Latest selectable date. |
weeksInMonth | number | - | The number of weeks in a month. This overrides the default set by the locale. |
isDateUnavailable | (date: DateValue) => boolean | - | Marks dates as unavailable. |
firstDayOfWeek | 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | - | Overrides the locale default for the first day of the week. |
pageBehavior | 'visible' | 'single' | 'visible' | Whether paging advances by the visible duration or one unit at a time. |
selectionAlignment | 'start' | 'center' | 'end' | 'center' | Aligns the visible range to the selection on initial render. |
isDisabled | boolean | false | Disables interaction and selection. |
isReadOnly | boolean | false | Keeps content readable but prevents selection changes. |
isInvalid | boolean | false | Marks the calendar as invalid for validation UI. |
visibleDuration | {months?: number; weeks?: number; days?: number} | {months: 1} | Visible time range. Use { months: n } for month view, { weeks: n } for week view, or { days: n } for day view. |
defaultYearPickerOpen | boolean | false | Initial open state of internal year picker. |
isYearPickerOpen | boolean | - | Controlled year picker open state. |
onYearPickerOpenChange | (isOpen: boolean) => void | - | Called when year picker open state changes. |
Composition Parts
| Component | Description |
|---|---|
Calendar.Header | Header container for navigation and heading. |
Calendar.Heading | Formatted heading for the visible range. Supports offset (for multi-month layouts) and format (month/year/day options). |
Calendar.NavButton | Previous/next navigation control (slot=\"previous\" or slot=\"next\"). |
Calendar.Grid | Day grid for one month (offset supported for multi-month layouts). |
Calendar.GridHeader | Weekday header container. |
Calendar.GridBody | Date cell body container. |
Calendar.HeaderCell | Weekday label cell. |
Calendar.Cell | Individual date cell. |
Calendar.CellIndicator | Optional indicator element for custom metadata. |
Calendar.YearPickerTrigger | Trigger to toggle year-picker mode. |
Calendar.YearPickerTriggerHeading | Localized heading content inside the year-picker trigger. |
Calendar.YearPickerTriggerIndicator | Toggle icon inside the year-picker trigger. |
Calendar.YearPickerGrid | Overlay year selection grid container. |
Calendar.YearPickerGridBody | Body renderer for year grid cells. |
Calendar.YearPickerCell | Individual year option cell. |
Year Picker Parts
Year picker subcomponents inherit formatting props from React Aria useCalendarHeading and useCalendarYearPicker.
| Component | Prop | Type | Default | Description |
|---|---|---|---|---|
Calendar.YearPickerTriggerHeading | format | DateFormatterOptions | - | Customize month/year label (e.g. {month: 'short'}). |
Calendar.YearPickerTriggerHeading | offset | {months?: number} | - | Shift the heading relative to the focused date (multi-month layouts). |
Calendar.YearPickerGrid | format | DateFormatterOptions | {year: 'numeric'} | Customize year cell labels (era, calendar system, etc.). |
Calendar.YearPickerGrid | visibleYears | number | min–max span or 20 | Number of years shown in the sliding window. Defaults to the full range between minValue and maxValue when both are set. |
Calendar.Cell Render Props
When Calendar.Cell children is a function, React Aria render props are available:
| Prop | Type | Description |
|---|---|---|
formattedDate | string | Localized day label for the cell. |
isSelected | boolean | Whether the date is selected. |
isUnavailable | boolean | Whether the date is unavailable. |
isDisabled | boolean | Whether the cell is disabled. |
isOutsideMonth | boolean | Whether the date belongs to adjacent month. |
For a complete list of supported calendar systems and their identifiers, see:
Related packages
@internationalized/date— date types (CalendarDate,CalendarDateTime,ZonedDateTime) and utilities used by all date componentsI18nProvider— override locale for a subtreeuseLocale— read the current locale and layout direction
Related Components
Composable date range picker with month grid, navigation, and year picker support built on React Aria RangeCalendar
DateFieldDate input field with labels, descriptions, and validation built on React Aria DateField
DatePickerComposable date picker built on React Aria DatePicker with DateField and Calendar composition