# DateRangePicker · HeroUI

> Migration guide for DateRangePicker from HeroUI v2 to v3

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

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


## Structure Changes

In v2, `DateRangePicker` was a single self-contained component with props for label, inputs, calendar, and time fields:

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

export default function App() {
  return (
    <DateRangePicker label="Trip dates" />
  );
}
```

In v3, DateRangePicker uses a composition-first API where you compose `DateField` and `RangeCalendar` explicitly:

```tsx
import { DateField, DateRangePicker, Label, RangeCalendar } from "@heroui/react";

export default function App() {
  return (
    <DateRangePicker>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose 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>
      </DateRangePicker.Popover>
    </DateRangePicker>
  );
}
```

## Key Changes

### 1. Component Structure

**v2:** Single `DateRangePicker` component with all parts (two inputs, separator, calendar, label, popover) handled internally via props
**v3:** Composition-based API with `DateRangePicker`, `DateField`, `RangeCalendar`, and `Label` composed together. Two `DateField.Input` components with `slot="start"` and `slot="end"` represent the range inputs.

### 2. Composition Parts

| v3 Component | Description |
|-----------|-------------|
| `DateRangePicker` | Root container and state owner |
| `DateRangePicker.Trigger` | Button that opens the calendar popover |
| `DateRangePicker.TriggerIndicator` | Calendar icon (default) or custom indicator |
| `DateRangePicker.RangeSeparator` | Separator between start and end date inputs (defaults to " - ") |
| `DateRangePicker.Popover` | Popover wrapper for the range calendar |
| `DateField.Group` | Input group wrapper |
| `DateField.InputContainer` | Container for start/end inputs and separator |
| `DateField.Input slot="start"` | Start date segmented input |
| `DateField.Input slot="end"` | End date segmented input |
| `DateField.Segment` | Individual date segment (month, day, year) |
| `DateField.Suffix` | Suffix slot for the trigger button |
| `RangeCalendar` | Full range calendar component (see [RangeCalendar migration](/systems/heroui/docs/migration/range-calendar)) |
| `Label` | External label component |

### 3. Prop Changes

| v2 Prop | v3 Equivalent | Notes |
|---------|---------------|-------|
| `value` | `value` | Same |
| `defaultValue` | `defaultValue` | Same |
| `onChange` | `onChange` | Same |
| `minValue` | `minValue` | Same |
| `maxValue` | `maxValue` | Same |
| `isDisabled` | `isDisabled` | Same |
| `isReadOnly` | `isReadOnly` | Same |
| `isRequired` | `isRequired` | Same |
| `isInvalid` | `isInvalid` | Same |
| `isOpen` | `isOpen` | Same |
| `defaultOpen` | `defaultOpen` | Same |
| `onOpenChange` | `onOpenChange` | Same |
| `granularity` | `granularity` | Same |
| `hourCycle` | `hourCycle` | Same |
| `hideTimeZone` | `hideTimeZone` | Same |
| `shouldForceLeadingZeros` | `shouldForceLeadingZeros` | Same |
| `allowsNonContiguousRanges` | - | Set on `RangeCalendar` directly |
| `pageBehavior` | - | Set on `RangeCalendar` directly |
| `label` | - | Use `Label` component |
| `description` | - | Use `Description` component |
| `errorMessage` | - | Use `FieldError` component |
| `variant` | - | Use `DateField.Group` `variant` prop or Tailwind CSS |
| `color` | - | Removed (use Tailwind CSS) |
| `size` | - | Removed (use Tailwind CSS) |
| `radius` | - | Removed (use Tailwind CSS) |
| `labelPlacement` | - | Control layout with Tailwind CSS |
| `selectorIcon` | - | Pass children to `DateRangePicker.TriggerIndicator` |
| `selectorButtonPlacement` | - | Compose trigger position via `DateField.Prefix` or `DateField.Suffix` |
| `visibleMonths` | - | Use `RangeCalendar` `visibleDuration` prop |
| `showMonthAndYearPickers` | - | Use `RangeCalendar.YearPickerTrigger` and `RangeCalendar.YearPickerGrid` |
| `calendarProps` | - | Pass props directly to `RangeCalendar` |
| `popoverProps` | - | Pass props directly to `DateRangePicker.Popover` |
| `selectorButtonProps` | - | Pass props directly to `DateRangePicker.Trigger` |
| `timeInputProps` | - | Compose time input separately |
| `calendarWidth` | - | Removed (use Tailwind CSS) |
| `validate` | - | Handle validation externally |
| `placeholderValue` | `placeholderValue` | Same |
| `autoFocus` | `autoFocus` | Same |
| `disableAnimation` | - | Removed |
| `classNames` | - | Use `className` on individual components |

## Migration Examples

### Basic DateRangePicker


  

#### v2


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

    <DateRangePicker label="Trip dates" />
    ```
  
  

#### v3


    ```tsx
    import { DateField, DateRangePicker, Label, RangeCalendar } from "@heroui/react";

    <DateRangePicker>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose 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>
      </DateRangePicker.Popover>
    </DateRangePicker>
    ```
  


### Controlled State


  

#### v2


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

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

    <DateRangePicker
      label="Trip dates"
      value={value}
      onChange={setValue}
    />
    ```
  
  

#### v3


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

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

    <DateRangePicker value={value} onChange={setValue}>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose 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>
      </DateRangePicker.Popover>
    </DateRangePicker>
    ```
  


### With Description and Error Message


  

#### v2


    ```tsx
    <DateRangePicker
      label="Trip dates"
      description="Select your travel period"
      errorMessage="End date must be after start date"
      isInvalid={isInvalid}
    />
    ```
  
  

#### v3


    ```tsx
    import { DateField, DateRangePicker, Label, Description, FieldError, RangeCalendar } from "@heroui/react";

    <DateRangePicker isInvalid={isInvalid}>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <Description>Select your travel period</Description>
      <FieldError>End date must be after start date</FieldError>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose trip dates">
          {/* RangeCalendar compound components */}
        </RangeCalendar>
      </DateRangePicker.Popover>
    </DateRangePicker>
    ```
  


### Custom Selector Icon


  

#### v2


    ```tsx
    import { Icon } from "@iconify/react";

    <DateRangePicker
      label="Trip dates"
      selectorIcon={<Icon icon="gravity-ui:calendar" />}
    />
    ```
  
  

#### v3


    ```tsx
    import { Icon } from "@iconify/react";

    <DateRangePicker>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator>
              <Icon icon="gravity-ui:calendar" />
            </DateRangePicker.TriggerIndicator>
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose trip dates">
          {/* RangeCalendar compound components */}
        </RangeCalendar>
      </DateRangePicker.Popover>
    </DateRangePicker>
    ```
  


### With Visible Months and Year Picker


  

#### v2


    ```tsx
    <DateRangePicker
      label="Trip dates"
      visibleMonths={2}
      showMonthAndYearPickers
    />
    ```
  
  

#### v3


    ```tsx
    <DateRangePicker>
      <Label>Trip dates</Label>
      <DateField.Group>
        <DateField.InputContainer>
          <DateField.Input slot="start">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
          <DateRangePicker.RangeSeparator />
          <DateField.Input slot="end">
            {(segment) => <DateField.Segment segment={segment} />}
          </DateField.Input>
        </DateField.InputContainer>
        <DateField.Suffix>
          <DateRangePicker.Trigger>
            <DateRangePicker.TriggerIndicator />
          </DateRangePicker.Trigger>
        </DateField.Suffix>
      </DateField.Group>
      <DateRangePicker.Popover>
        <RangeCalendar aria-label="Choose trip dates" visibleDuration={{months: 2}}>
          <RangeCalendar.Header>
            <RangeCalendar.YearPickerTrigger>
              <RangeCalendar.YearPickerTriggerHeading />
              <RangeCalendar.YearPickerTriggerIndicator />
            </RangeCalendar.YearPickerTrigger>
            <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.YearPickerGrid>
            <RangeCalendar.YearPickerGridBody>
              {(year) => <RangeCalendar.YearPickerCell date={year} />}
            </RangeCalendar.YearPickerGridBody>
          </RangeCalendar.YearPickerGrid>
        </RangeCalendar>
      </DateRangePicker.Popover>
    </DateRangePicker>
    ```
  


## Styling Changes

### v2: `classNames` Prop

```tsx
<DateRangePicker
  classNames={{
    base: "custom-base",
    label: "custom-label",
    selectorButton: "custom-trigger",
    selectorIcon: "custom-icon",
    separator: "custom-separator",
    popoverContent: "custom-popover",
    calendar: "custom-calendar",
  }}
/>
```

### v3: Direct `className` Props

```tsx
<DateRangePicker className="custom-base">
  <Label className="custom-label">Trip dates</Label>
  <DateField.Group>
    <DateField.InputContainer>
      <DateField.Input slot="start">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
      <DateRangePicker.RangeSeparator className="custom-separator" />
      <DateField.Input slot="end">
        {(segment) => <DateField.Segment segment={segment} />}
      </DateField.Input>
    </DateField.InputContainer>
    <DateField.Suffix>
      <DateRangePicker.Trigger className="custom-trigger">
        <DateRangePicker.TriggerIndicator className="custom-icon" />
      </DateRangePicker.Trigger>
    </DateField.Suffix>
  </DateField.Group>
  <DateRangePicker.Popover className="custom-popover">
    <RangeCalendar aria-label="Choose trip dates" className="custom-calendar">
      {/* RangeCalendar compound components */}
    </RangeCalendar>
  </DateRangePicker.Popover>
</DateRangePicker>
```

## Component Anatomy

The v3 DateRangePicker follows this structure:

```
DateRangePicker (Root)
  ├── Label
  ├── DateField.Group
  │   ├── DateField.InputContainer
  │   │   ├── DateField.Input slot="start"
  │   │   │   └── DateField.Segment (render prop per segment)
  │   │   ├── DateRangePicker.RangeSeparator
  │   │   └── DateField.Input slot="end"
  │   │       └── DateField.Segment (render prop per segment)
  │   └── DateField.Suffix
  │       └── DateRangePicker.Trigger
  │           └── DateRangePicker.TriggerIndicator
  ├── Description (optional)
  ├── FieldError (optional)
  └── DateRangePicker.Popover
      └── RangeCalendar (see RangeCalendar migration guide)
```

## Summary

1. **Component Structure**: Single component → composition of `DateRangePicker`, `DateField`, `RangeCalendar`, and `Label`
2. **Dual Inputs**: Built-in start/end inputs → two `DateField.Input` with `slot="start"` and `slot="end"`
3. **Separator**: Built-in → `DateRangePicker.RangeSeparator` component
4. **Label**: `label` prop → `Label` component
5. **Description/Error**: Props → `Description` and `FieldError` components
6. **Calendar**: Built-in → compose `RangeCalendar` with its own compound components inside `DateRangePicker.Popover`
7. **Selector Icon**: `selectorIcon` prop → pass children to `DateRangePicker.TriggerIndicator`
8. **Year Picker**: `showMonthAndYearPickers` prop → use `RangeCalendar.YearPickerTrigger` and `RangeCalendar.YearPickerGrid`
9. **Styling Props Removed**: `variant`, `color`, `size`, `radius` → use Tailwind CSS or `DateField.Group` variant
10. **ClassNames Removed**: Use `className` on individual compound components

---

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