# Calendar · HeroUI

> Migration guide for Calendar from HeroUI v2 to v3

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

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


## Structure Changes

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

```tsx
import { Calendar } from "@heroui/react";

export default function App() {
  return <Calendar aria-label="Date" />;
}
```

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

```tsx
import { Calendar } from "@heroui/react";

export default function App() {
  return (
    <Calendar aria-label="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>
  );
}
```

## Key Changes

### 1. Component Structure

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

### 2. Year Picker

**v2:** Built-in month/year pickers via `showMonthAndYearPickers` prop
**v3:** Dedicated compound components: `Calendar.YearPickerTrigger`, `Calendar.YearPickerGrid`, `Calendar.YearPickerGridBody`, `Calendar.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 |
| `isDisabled` | `isDisabled` | Same |
| `isReadOnly` | `isReadOnly` | Same |
| `isInvalid` | `isInvalid` | Same |
| `visibleMonths` | `visibleDuration` | Changed to `{months: number}` object |
| `showMonthAndYearPickers` | - | Use `Calendar.YearPickerTrigger` and `Calendar.YearPickerGrid` |
| `onHeaderExpandedChange` | `onYearPickerOpenChange` | Renamed |
| `color` | - | Removed (use Tailwind CSS) |
| `calendarWidth` | - | Removed (use `className` or Tailwind CSS) |
| `weekdayStyle` | - | Removed |
| `pageBehavior` | `pageBehavior` | Same |
| `firstDayOfWeek` | - | Use `I18nProvider` locale instead |
| `hideDisabledDates` | - | Removed |
| `disableAnimation` | - | Removed |
| `topContent` | - | Place custom content inside `Calendar` before `Calendar.Grid` |
| `bottomContent` | - | Place custom content inside `Calendar` after `Calendar.Grid` |
| `classNames` | - | Use `className` on individual compound components |
| `errorMessage` | - | Removed (handle validation externally) |

### 4. Color Prop Removed

**v2:** `color` prop with `default`, `primary`, `secondary`, `success`, `warning`, `danger`
**v3:** No `color` prop — style cells using Tailwind CSS classes or customize via `calendar.css` overrides

## Migration Examples

### Basic Calendar


  

#### v2


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

    <Calendar
      aria-label="Date"
      defaultValue={today(getLocalTimeZone())}
    />
    ```
  
  

#### v3


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

    <Calendar
      aria-label="Date"
      defaultValue={today(getLocalTimeZone())}
    >
      <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>
    ```
  


### Controlled State


  

#### v2


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

    const [value, setValue] = useState(parseDate("2024-03-07"));

    <Calendar
      aria-label="Date"
      value={value}
      onChange={setValue}
    />
    ```
  
  

#### v3


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

    const [value, setValue] = useState(parseDate("2024-03-07"));

    <Calendar
      aria-label="Date"
      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>
    ```
  


### Month and Year Pickers


  

#### v2


    ```tsx
    <Calendar
      aria-label="Date"
      showMonthAndYearPickers
    />
    ```
  
  

#### v3


    ```tsx
    <Calendar aria-label="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 date={year} />}
        </Calendar.YearPickerGridBody>
      </Calendar.YearPickerGrid>
    </Calendar>
    ```
  


### Multiple Months


  

#### v2


    ```tsx
    <Calendar
      aria-label="Date"
      visibleMonths={2}
    />
    ```
  
  

#### v3


    ```tsx
    <Calendar aria-label="Date" visibleDuration={{months: 2}}>
      <Calendar.Header>
        <Calendar.Heading />
        <Calendar.NavButton slot="previous" />
        <Calendar.NavButton slot="next" />
      </Calendar.Header>
      <div className="flex gap-4">
        <Calendar.Grid>
          <Calendar.GridHeader>
            {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
          </Calendar.GridHeader>
          <Calendar.GridBody>
            {(date) => <Calendar.Cell date={date} />}
          </Calendar.GridBody>
        </Calendar.Grid>
        <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>
    </Calendar>
    ```
  


### Unavailable Dates


  

#### v2


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

    const { locale } = useLocale();

    <Calendar
      aria-label="Date"
      isDateUnavailable={(date) => isWeekend(date, locale)}
    />
    ```
  
  

#### v3


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

    const { locale } = useLocale();

    <Calendar
      aria-label="Date"
      isDateUnavailable={(date) => isWeekend(date, locale)}
    >
      <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>
    ```
  


### Top and Bottom Content


  

#### v2


    ```tsx
    <Calendar
      aria-label="Date"
      topContent={<p>Select a date</p>}
      bottomContent={
        <button onClick={() => setValue(today(getLocalTimeZone()))}>
          Today
        </button>
      }
    />
    ```
  
  

#### v3


    ```tsx
    <Calendar aria-label="Date">
      <p>Select a date</p>
      <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>
      <button onClick={() => setValue(today(getLocalTimeZone()))}>
        Today
      </button>
    </Calendar>
    ```
  


### Cell Indicators


  

#### v2


    ```tsx
    {/* v2 did not have a built-in cell indicator API */}
    ```
  
  

#### v3


    ```tsx
    <Calendar.Grid>
      <Calendar.GridHeader>
        {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
      </Calendar.GridHeader>
      <Calendar.GridBody>
        {(date) => (
          <Calendar.Cell date={date}>
            {({formattedDate}) => (
              <>
                {formattedDate}
                {hasEvent(date) && <Calendar.CellIndicator />}
              </>
            )}
          </Calendar.Cell>
        )}
      </Calendar.GridBody>
    </Calendar.Grid>
    ```
  


## Styling Changes

### v2: `classNames` Prop

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

## Component Anatomy

The v3 Calendar follows this structure:

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

## Summary

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

---

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