);
}
```
### Min and Max Dates
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function MinMaxDates() {
const now = today(getLocalTimeZone());
const minDate = now;
const maxDate = now.add({months: 3});
return (
{(day) => {day}}
{(date) => }
Select dates between today and {maxDate.toString()}
);
}
```
### Unavailable Dates
Use `isDateUnavailable` to block dates such as weekends, holidays, or booked slots.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function UnavailableDates() {
const now = today(getLocalTimeZone());
const blockedRanges = [
[now.add({days: 2}), now.add({days: 5})],
[now.add({days: 12}), now.add({days: 13})],
] as const;
const isDateUnavailable = (date: DateValue) => {
return blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0);
};
return (
{(day) => {day}}
{(date) => }
Some days are unavailable
);
}
```
### 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).
```tsx
"use client";
import type {CalendarDate, DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function AnchorUnavailableDates() {
const now = today(getLocalTimeZone());
const isDateUnavailable = (date: DateValue, anchorDate: CalendarDate | null) => {
return anchorDate != null && Math.abs(date.compare(anchorDate)) > 7;
};
return (
{(day) => {day}}
{(date) => }
After selecting a start date, only dates within 7 days are available
);
}
```
### Weeks in Month
Set `weeksInMonth` to a fixed value (for example, `6`) to keep the grid height stable when navigating between months.
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
export function WeeksInMonth() {
return (
{(day) => {day}}
{(date) => }
Always shows 6 weeks per month to avoid layout shift when navigating
);
}
```
### 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.
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const weekOptions = [
{id: "1", name: "1 week"},
{id: "2", name: "2 weeks"},
{id: "3", name: "3 weeks"},
{id: "4", name: "4 weeks"},
{id: "5", name: "5 weeks"},
{id: "6", name: "6 weeks"},
{id: "8", name: "8 weeks"},
] as const;
export function WeekView() {
const [weeks, setWeeks] = useState(1);
return (
{(day) => {day}}
{(date) => }
);
}
```
### 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.
```tsx
"use client";
import {Label, ListBox, RangeCalendar, Select} from "@heroui/react";
import {useState} from "react";
const dayOptions = [
{id: "1", name: "1 day"},
{id: "5", name: "5 days"},
{id: "7", name: "7 days"},
{id: "8", name: "8 days"},
{id: "10", name: "10 days"},
{id: "14", name: "14 days"},
{id: "21", name: "21 days"},
] as const;
export function DayView() {
const [days, setDays] = useState(5);
return (
{(day) => {day}}
{(date) => }
);
}
```
### Allows Non-Contiguous Ranges
Enable `allowsNonContiguousRanges` to allow selection across unavailable dates.
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function AllowsNonContiguousRanges() {
const now = today(getLocalTimeZone());
const blockedRanges = [
[now.add({days: 2}), now.add({days: 5})],
[now.add({days: 12}), now.add({days: 13})],
] as const;
const isDateUnavailable = (date: DateValue) => {
return blockedRanges.some(([start, end]) => date.compare(start) >= 0 && date.compare(end) <= 0);
};
return (
{(day) => {day}}
{(date) => }
Non-contiguous ranges are allowed across unavailable dates
);
}
```
### Read Only
```tsx
"use client";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
export function ReadOnly() {
return (
{(day) => {day}}
{(date) => }
Range calendar is read-only
);
}
```
### Invalid
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Description, RangeCalendar} from "@heroui/react";
import {getLocalTimeZone, today} from "@internationalized/date";
import {useState} from "react";
type DateRange = {
start: DateValue;
end: DateValue;
};
export function Invalid() {
const now = today(getLocalTimeZone());
const [value, setValue] = useState({
end: now.add({days: 14}),
start: now.add({days: 6}),
});
const isInvalid = value.end.compare(value.start) > 7;
return (
{(day) => {day}}
{(date) => }
{isInvalid ? (
Maximum stay duration is 1 week
) : (
Select a stay of up to 7 days
)}
);
}
```
### Focused Value
```tsx
"use client";
import type {DateValue} from "@internationalized/date";
import {Button, Description, RangeCalendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
import {useState} from "react";
export function FocusedValue() {
const [focusedDate, setFocusedDate] = useState(parseDate("2025-06-15"));
return (
);
}
```
### Multiple Months
Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
export function MultipleMonths() {
return (
{(day) => {day}}
{(date) => }
{(day) => {day}}
{(date) => }
);
}
```
### 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string).
The example below shows the Indian calendar system:
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {I18nProvider} from "react-aria-components";
export function InternationalCalendar() {
return (
{(day) => {day}}
{(date) => }
{({year}) => }
);
}
```
**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.
## Customization
### Tailwind CSS
```tsx
"use client";
import {RangeCalendar} from "@heroui/react";
import {parseDate} from "@internationalized/date";
const cellClassName = [
"rounded-md",
"**:data-[slot=range-calendar-cell-button]:rounded-md",
"data-[outside-month=true]:text-muted data-[outside-month=true]:opacity-50",
"data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-default",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft",
"data-[today=true]:**:data-[slot=range-calendar-cell-button]:text-success-soft-foreground",
"data-[today=true]:data-[hovered=true]:not-data-[selected=true]:**:data-[slot=range-calendar-cell-button]:bg-success-soft-hover",
"data-[selected=true]:rounded-none data-[selected=true]:bg-success-soft",
"data-[outside-month=true]:data-[selected=true]:bg-default/20",
"data-[selection-start=true]:rounded-tl-md data-[selection-start=true]:rounded-bl-md",
"data-[selection-end=true]:rounded-tr-md data-[selection-end=true]:rounded-br-md",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-start=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-start=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:bg-success",
"data-[selection-end=true]:**:data-[slot=range-calendar-cell-button]:text-success-foreground",
"data-[selection-end=true]:data-[pressed=true]:**:data-[slot=range-calendar-cell-button]:bg-success-hover",
].join(" ");
export function CustomStyles() {
return (
{(day) => (
{day}
)}
{(date) => }
);
}
```
### Global CSS
```css
@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;
}
}
```
## Styling Reference
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### 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**: `:hover` or `[data-hovered="true"]`
- **Pressed**: `:active` or `[data-pressed="true"]`
- **Focus visible**: `:focus-visible` or `[data-focus-visible="true"]`
- **Disabled**: `:disabled` or `[data-disabled="true"]`
## API Reference
### RangeCalendar
RangeCalendar inherits all props from React Aria [RangeCalendar](https://react-spectrum.adobe.com/react-aria/RangeCalendar.html).
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `RangeValue \| null` | - | Controlled selected range. |
| `defaultValue` | `RangeValue \| null` | - | Initial selected range (uncontrolled). |
| `onChange` | `(value: RangeValue) => 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`](https://react-aria.adobe.com/useCalendar#usecalendarheading) and [`useCalendarYearPicker`](https://react-aria.adobe.com/useCalendar#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:
- [React Aria Calendar Implementations](https://react-aria.adobe.com/internationalized/date/Calendar#implementations)
- [React Aria International Calendars](https://react-aria.adobe.com/Calendar#international-calendars)
### Related packages
- [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — date types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`) and utilities used by all date components
- [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree
- [`useLocale`](https://react-aria.adobe.com/useLocale) — read the current locale and layout direction
## Related Components
## Related Components
- **Calendar**: Interactive month grid for selecting dates
- **DateField**: Date input field with labels, descriptions, and validation
- **DatePicker**: Composable date picker with date field trigger and calendar popover