# RangeCalendar · HeroUI

> Migration guide for RangeCalendar from HeroUI v2 to v3

- Page: https://jiguang132.com/systems/heroui/docs/migration/range-calendar
- Based on: HeroUI documentation (Apache-2.0) @ 60edd8ed751b1d2a270623fbb182bbcfa8baa9af

> 
  Refer to the [v3 RangeCalendar documentation](/systems/heroui/docs/components/range-calendar) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.


## Structure Changes

In v2, `RangeCalendar` was a single component configured entirely through props:

```tsx
import { RangeCalendar } from "@heroui/react";
import { today, getLocalTimeZone } from "@internationalized/date";

export default function App() {
  return (
    <RangeCalendar
      aria-label="Trip dates"
      defaultValue={{
        start: today(getLocalTimeZone()),
        end: today(getLocalTimeZone()).add({ weeks: 1 }),
      }}
    />
  );
}
```

In v3, RangeCalendar uses a compound component pattern with explicit subcomponents:

```tsx
import { RangeCalendar } from "@heroui/react";
import { today, getLocalTimeZone } from "@internationalized/date";

export default function App() {
  return (
    <RangeCalendar
      aria-label="Trip dates"
      defaultValue={{
        start: today(getLocalTimeZone()),
        end: today(getLocalTimeZone()).add({ weeks: 1 }),
      }}
    >
      <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>
  );
}
```

## Key Changes

### 1. Component Structure

**v2:** Single `RangeCalendar` component with all layout handled internally
**v3:** Compound components: `RangeCalendar.Header`, `RangeCalendar.Heading`, `RangeCalendar.NavButton`, `RangeCalendar.Grid`, `RangeCalendar.GridHeader`, `RangeCalendar.GridBody`, `RangeCalendar.HeaderCell`, `RangeCalendar.Cell`, `RangeCalendar.CellIndicator`

### 2. Year Picker

**v2:** Built-in month/year pickers via `showMonthAndYearPickers` prop
**v3:** Dedicated compound components: `RangeCalendar.YearPickerTrigger`, `RangeCalendar.YearPickerGrid`, `RangeCalendar.YearPickerGridBody`, `RangeCalendar.YearPickerCell`

### 3. Prop Changes

| v2 Prop | v3 Equivalent | Notes |
|---------|---------------|-------|
| `value` | `value` | Same |
| `defaultValue` | `defaultValue` | Same |
| `onChange` | `onChange` | Same |
| `focusedValue` | `focusedValue` | Same |
| `onFocusChange` | `onFocusChange` | Same |
| `minValue` | `minValue` | Same |
| `maxValue` | `maxValue` | Same |
| `isDateUnavailable` | `isDateUnavailable` | Same |
| `allowsNonContiguousRanges` | `allowsNonContiguousRanges` | Same |
| `isDisabled` | `isDisabled` | Same |
| `isReadOnly` | `isReadOnly` | Same |
| `isInvalid` | `isInvalid` | Same |
| `pageBehavior` | `pageBehavior` | Same |
| `visibleMonths` | `visibleDuration` | Changed to `{months: number}` object |
| `showMonthAndYearPickers` | - | Use `RangeCalendar.YearPickerTrigger` and `RangeCalendar.YearPickerGrid` |
| `onHeaderExpandedChange` | `onYearPickerOpenChange` | Renamed |
| `color` | - | Removed (use Tailwind CSS) |
| `calendarWidth` | - | Removed (use `className` or Tailwind CSS) |
| `weekdayStyle` | - | Removed |
| `firstDayOfWeek` | - | Use `I18nProvider` locale instead |
| `topContent` | - | Place custom content inside `RangeCalendar` before the grid |
| `bottomContent` | - | Place custom content inside `RangeCalendar` after the grid |
| `errorMessage` | - | Removed (handle validation externally) |
| `showHelper` | - | Removed |
| `disableAnimation` | - | Removed |
| `classNames` | - | Use `className` on individual compound components |

## Migration Examples

### Basic Range Selection


  

#### v2


    ```tsx
    import { RangeCalendar } from "@heroui/react";
    import { today, getLocalTimeZone } from "@internationalized/date";

    <RangeCalendar
      aria-label="Trip dates"
      defaultValue={{
        start: today(getLocalTimeZone()),
        end: today(getLocalTimeZone()).add({ weeks: 1 }),
      }}
    />
    ```
  
  

#### v3


    ```tsx
    import { RangeCalendar } from "@heroui/react";
    import { today, getLocalTimeZone } from "@internationalized/date";

    <RangeCalendar
      aria-label="Trip dates"
      defaultValue={{
        start: today(getLocalTimeZone()),
        end: today(getLocalTimeZone()).add({ weeks: 1 }),
      }}
    >
      <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>
    ```
  


### Controlled State


  

#### v2


    ```tsx
    import { useState } from "react";
    import { RangeCalendar } from "@heroui/react";
    import { parseDate } from "@internationalized/date";

    const [value, setValue] = useState({
      start: parseDate("2024-03-01"),
      end: parseDate("2024-03-07"),
    });

    <RangeCalendar
      aria-label="Trip dates"
      value={value}
      onChange={setValue}
    />
    ```
  
  

#### v3


    ```tsx
    import { useState } from "react";
    import { RangeCalendar } from "@heroui/react";
    import { parseDate } from "@internationalized/date";

    const [value, setValue] = useState({
      start: parseDate("2024-03-01"),
      end: parseDate("2024-03-07"),
    });

    <RangeCalendar
      aria-label="Trip dates"
      value={value}
      onChange={setValue}
    >
      <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>
    ```
  


### Month and Year Pickers


  

#### v2


    ```tsx
    <RangeCalendar
      aria-label="Trip dates"
      showMonthAndYearPickers
    />
    ```
  
  

#### v3


    ```tsx
    <RangeCalendar aria-label="Trip dates">
      <RangeCalendar.Header>
        <RangeCalendar.YearPickerTrigger>
          <RangeCalendar.YearPickerTriggerHeading />
          <RangeCalendar.YearPickerTriggerIndicator />
        </RangeCalendar.YearPickerTrigger>
        <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.YearPickerGrid>
        <RangeCalendar.YearPickerGridBody>
          {(year) => <RangeCalendar.YearPickerCell date={year} />}
        </RangeCalendar.YearPickerGridBody>
      </RangeCalendar.YearPickerGrid>
    </RangeCalendar>
    ```
  


### Multiple Months


  

#### v2


    ```tsx
    <RangeCalendar
      aria-label="Trip dates"
      visibleMonths={2}
    />
    ```
  
  

#### v3


    ```tsx
    <RangeCalendar aria-label="Trip dates" visibleDuration={{months: 2}}>
      <RangeCalendar.Header>
        <RangeCalendar.Heading />
        <RangeCalendar.NavButton slot="previous" />
        <RangeCalendar.NavButton slot="next" />
      </RangeCalendar.Header>
      <div className="flex gap-4">
        <RangeCalendar.Grid>
          <RangeCalendar.GridHeader>
            {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
          </RangeCalendar.GridHeader>
          <RangeCalendar.GridBody>
            {(date) => <RangeCalendar.Cell date={date} />}
          </RangeCalendar.GridBody>
        </RangeCalendar.Grid>
        <RangeCalendar.Grid offset={{months: 1}}>
          <RangeCalendar.GridHeader>
            {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
          </RangeCalendar.GridHeader>
          <RangeCalendar.GridBody>
            {(date) => <RangeCalendar.Cell date={date} />}
          </RangeCalendar.GridBody>
        </RangeCalendar.Grid>
      </div>
    </RangeCalendar>
    ```
  


### Unavailable Dates with Non-Contiguous Ranges


  

#### v2


    ```tsx
    import { isWeekend } from "@internationalized/date";
    import { useLocale } from "@react-aria/i18n";

    const { locale } = useLocale();

    <RangeCalendar
      aria-label="Trip dates"
      isDateUnavailable={(date) => isWeekend(date, locale)}
      allowsNonContiguousRanges
    />
    ```
  
  

#### v3


    ```tsx
    import { isWeekend } from "@internationalized/date";
    import { useLocale } from "@react-aria/i18n";

    const { locale } = useLocale();

    <RangeCalendar
      aria-label="Trip dates"
      isDateUnavailable={(date) => isWeekend(date, locale)}
      allowsNonContiguousRanges
    >
      <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>
    ```
  


### Top and Bottom Content


  

#### v2


    ```tsx
    <RangeCalendar
      aria-label="Trip dates"
      topContent={<p>Select your travel dates</p>}
      bottomContent={
        <button onClick={handleReset}>Reset</button>
      }
    />
    ```
  
  

#### v3


    ```tsx
    <RangeCalendar aria-label="Trip dates">
      <p>Select your travel dates</p>
      <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>
      <button onClick={handleReset}>Reset</button>
    </RangeCalendar>
    ```
  


## Styling Changes

### v2: `classNames` Prop

```tsx
<RangeCalendar
  classNames={{
    base: "custom-base",
    prevButton: "custom-prev",
    nextButton: "custom-next",
    title: "custom-title",
    cell: "custom-cell",
    cellButton: "custom-cell-button",
  }}
/>
```

### v3: Direct `className` Props

```tsx
<RangeCalendar className="custom-base">
  <RangeCalendar.Header>
    <RangeCalendar.Heading className="custom-title" />
    <RangeCalendar.NavButton slot="previous" className="custom-prev" />
    <RangeCalendar.NavButton slot="next" className="custom-next" />
  </RangeCalendar.Header>
  <RangeCalendar.Grid>
    <RangeCalendar.GridHeader>
      {(day) => <RangeCalendar.HeaderCell>{day}</RangeCalendar.HeaderCell>}
    </RangeCalendar.GridHeader>
    <RangeCalendar.GridBody>
      {(date) => <RangeCalendar.Cell date={date} className="custom-cell" />}
    </RangeCalendar.GridBody>
  </RangeCalendar.Grid>
</RangeCalendar>
```

## Component Anatomy

The v3 RangeCalendar follows this structure:

```
RangeCalendar (Root)
  ├── [Custom top content]
  ├── RangeCalendar.Header
  │   ├── RangeCalendar.Heading (or RangeCalendar.YearPickerTrigger)
  │   ├── RangeCalendar.NavButton slot="previous"
  │   └── RangeCalendar.NavButton slot="next"
  ├── RangeCalendar.Grid (one per visible month)
  │   ├── RangeCalendar.GridHeader
  │   │   └── RangeCalendar.HeaderCell (render prop)
  │   └── RangeCalendar.GridBody
  │       └── RangeCalendar.Cell (render prop)
  │           └── RangeCalendar.CellIndicator (optional)
  ├── RangeCalendar.YearPickerGrid (optional)
  │   └── RangeCalendar.YearPickerGridBody
  │       └── RangeCalendar.YearPickerCell
  └── [Custom bottom content]
```

## Summary

1. **Component Structure**: Single component → compound components with explicit layout control
2. **Year Picker**: `showMonthAndYearPickers` prop → dedicated `RangeCalendar.YearPickerTrigger` and `RangeCalendar.YearPickerGrid` components
3. **Multiple Months**: `visibleMonths={n}` → `visibleDuration={{months: n}}` with multiple `RangeCalendar.Grid` components using `offset`
4. **Color Removed**: Use Tailwind CSS classes instead
5. **Top/Bottom Content**: Props removed → place content directly as children inside `RangeCalendar`
6. **Cell Customization**: New `RangeCalendar.CellIndicator` and render prop support on `RangeCalendar.Cell`
7. **Styling**: `classNames` prop → `className` on individual compound components
8. **Removed Props**: `calendarWidth`, `weekdayStyle`, `showHelper`, `errorMessage`, `disableAnimation` — use Tailwind CSS or handle externally

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
