RangeCalendar
Composable date range picker with month grid, navigation, and year picker support built on React Aria RangeCalendar
Import
import { RangeCalendar } from '@heroui/react';Usage
Anatomy
import {RangeCalendar} from '@heroui/react';
export default () => (
<RangeCalendar aria-label="Trip dates">
<RangeCalendar.Header>
<RangeCalendar.Heading />
<RangeCalendar.NavButton slot="previous" />
<RangeCalendar.NavButton slot="next" />
</RangeCalendar.Header>
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
{(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
{(date) => <RangeCalendar.Cell date={date} />}
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</RangeCalendar>
)Year Picker
RangeCalendar.YearPickerTrigger, RangeCalendar.YearPickerGrid, and their body/cell subcomponents provide an integrated year navigation pattern.
Default Value
Controlled
Min and Max Dates
Unavailable Dates
Use isDateUnavailable to block dates such as weekends, holidays, or booked slots.
Anchor-Based Unavailable Dates
When selecting a range, isDateUnavailable receives a second argument, anchorDate, set to the first selected date. Use it to limit which end dates are valid (for example, within 7 days of the start).
Weeks in Month
Set weeksInMonth to a fixed value (for example, 6) to keep the grid height stable when navigating between months.
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.
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.
Allows Non-Contiguous Ranges
Enable allowsNonContiguousRanges to allow selection across unavailable dates.
Disabled
Read Only
Invalid
Focused Value
Cell Indicators
You can customize RangeCalendar.Cell children and use RangeCalendar.CellIndicator to display metadata like events.
Multiple Months
Render multiple grids with visibleDuration and offset for booking and planning experiences. Use RangeCalendar.Heading with an offset (for example, offset={{ months: 1 }}) in each column header to label that month.
International Calendars
By default, RangeCalendar displays dates using the calendar system for the user's locale. You can override this by wrapping your RangeCalendar with I18nProvider and setting the Unicode calendar locale extension.
The example below shows the Indian calendar system:
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.
Real-World Example
Styling
Passing Tailwind CSS classes
import {RangeCalendar} from '@heroui/react';
function CustomRangeCalendar() {
return (
<RangeCalendar aria-label="Trip dates" className="w-80 rounded-2xl border border-border bg-surface p-3 shadow-sm">
<RangeCalendar.Header className="pb-3">
<RangeCalendar.Heading className="text-default" />
<RangeCalendar.NavButton slot="previous" className="text-default" />
<RangeCalendar.NavButton slot="next" className="text-default" />
</RangeCalendar.Header>
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
{(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
{(date) => <RangeCalendar.Cell date={date} />}
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</RangeCalendar>
);
}Customizing the component classes
@layer components {
.range-calendar {
@apply w-80 rounded-2xl border border-border bg-surface p-3 shadow-sm;
}
.range-calendar__heading {
@apply text-sm font-semibold text-default;
}
.range-calendar__cell[data-selected="true"] .range-calendar__cell-button {
@apply bg-accent text-accent-foreground;
}
}CSS Classes
RangeCalendar uses these classes in packages/styles/components/range-calendar.css and packages/styles/components/calendar-year-picker.css:
.range-calendar- Root container..range-calendar__header- Header row containing nav buttons and heading..range-calendar__heading- Current month label..range-calendar__nav-button- Previous/next navigation controls..range-calendar__grid- Main day grid..range-calendar__grid-header- Weekday header row wrapper..range-calendar__grid-body- Date rows wrapper..range-calendar__header-cell- Weekday header cell..range-calendar__cell- Interactive day cell wrapper..range-calendar__cell-button- Interactive day button inside each cell..range-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
RangeCalendar supports both pseudo-classes and React Aria data attributes:
- Selected:
[data-selected="true"] - Selection start:
[data-selection-start="true"] - Selection end:
[data-selection-end="true"] - Range middle:
[data-selection-in-range="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
RangeCalendar Props
RangeCalendar inherits all props from React Aria RangeCalendar.
| Prop | Type | Default | Description |
|---|---|---|---|
value | RangeValue<DateValue> | null | - | Controlled selected range. |
defaultValue | RangeValue<DateValue> | null | - | Initial selected range (uncontrolled). |
onChange | (value: RangeValue<DateValue>) => 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, anchorDate: CalendarDate | null) => boolean | - | Marks dates as unavailable. When anchorDate is set, it is the first date the user selected in the current range gesture. |
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. |
allowsNonContiguousRanges | boolean | false | Allows ranges that span unavailable dates. |
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 |
|---|---|
RangeCalendar.Header | Header container for navigation and heading. |
RangeCalendar.Heading | Formatted heading for the visible range. Supports offset (for multi-month layouts) and format (month/year/day options). |
RangeCalendar.NavButton | Previous/next navigation control (slot="previous" or slot="next"). |
RangeCalendar.Grid | Day grid for one month (offset supported for multi-month layouts). |
RangeCalendar.GridHeader | Weekday header container. |
RangeCalendar.GridBody | Date cell body container. |
RangeCalendar.HeaderCell | Weekday label cell. |
RangeCalendar.Cell | Individual date cell. |
RangeCalendar.CellIndicator | Optional indicator element for custom metadata. |
RangeCalendar.YearPickerTrigger | Trigger to toggle year-picker mode. |
RangeCalendar.YearPickerTriggerHeading | Localized heading content inside the year-picker trigger. |
RangeCalendar.YearPickerTriggerIndicator | Toggle icon inside the year-picker trigger. |
RangeCalendar.YearPickerGrid | Overlay year selection grid container. |
RangeCalendar.YearPickerGridBody | Body renderer for year grid cells. |
RangeCalendar.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 |
|---|---|---|---|---|
RangeCalendar.YearPickerTriggerHeading | format | DateFormatterOptions | - | Customize month/year label (e.g. {month: 'short'}). |
RangeCalendar.YearPickerTriggerHeading | offset | {months?: number} | - | Shift the heading relative to the focused date (multi-month layouts). |
RangeCalendar.YearPickerGrid | format | DateFormatterOptions | {year: 'numeric'} | Customize year cell labels (era, calendar system, etc.). |
RangeCalendar.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. |
RangeCalendar.Cell Render Props
When RangeCalendar.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. |
isSelectionStart | boolean | Whether the date is the start of the selected range. |
isSelectionEnd | boolean | Whether the date is the end of the selected range. |
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





