---
title: RangeCalendar
description: Composable date range picker with month grid, navigation, and year picker support built on React Aria RangeCalendar

links:
  rac: RangeCalendar
  source: range-calendar/range-calendar.tsx
  styles: range-calendar.css
  storybook: Components/Date and Time/RangeCalendar
---

## Usage

```tsx
import { RangeCalendar } from '@heroui/react';
```

<ComponentPreview name="range-calendar-basic" />

## Anatomy

```tsx
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>
)
```

## Examples

### Disabled

<ComponentPreview name="range-calendar-disabled" />

### Year Picker

`RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, and their body/cell subcomponents provide an integrated year navigation pattern.

<ComponentPreview name="range-calendar-year-picker" />

### Default Value

<ComponentPreview name="range-calendar-default-value" />

### Controlled

<ComponentPreview name="range-calendar-controlled" />

### Min and Max Dates

<ComponentPreview name="range-calendar-min-max-dates" />

### Unavailable Dates

Use `isDateUnavailable` to block dates such as weekends, holidays, or booked slots.

<ComponentPreview name="range-calendar-unavailable-dates" />

### 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).

<ComponentPreview name="range-calendar-anchor-unavailable-dates" />

### Weeks in Month

Set `weeksInMonth` to a fixed value (for example, `6`) to keep the grid height stable when navigating between months.

<ComponentPreview name="range-calendar-weeks-in-month" />

### 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.

<ComponentPreview name="range-calendar-week-view" />

### 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.

<ComponentPreview name="range-calendar-day-view" />

### Allows Non-Contiguous Ranges

Enable `allowsNonContiguousRanges` to allow selection across unavailable dates.

<ComponentPreview name="range-calendar-allows-non-contiguous-ranges" />

### Read Only

<ComponentPreview name="range-calendar-read-only" />

### Invalid

<ComponentPreview name="range-calendar-invalid" />

### Focused Value

<ComponentPreview name="range-calendar-focused-value" />

### Cell Indicators

You can customize `RangeCalendar.Cell` children and use `RangeCalendar.CellIndicator` to display metadata like events.

<ComponentPreview name="range-calendar-with-indicators" />

### Real-World Example

<ComponentPreview name="range-calendar-booking-calendar" />

### Multiple Months

Render multiple grids with `visibleDuration` and `offset` for booking and planning experiences.

<ComponentPreview name="range-calendar-multiple-months" />

### 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:

<ComponentPreview name="range-calendar-international-calendar" />

**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

<ComponentPreview name="range-calendar-custom-styles" />

### 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<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`](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

<RelatedComponents component="range-calendar" />
