DateRangePicker
Migration guide for DateRangePicker from HeroUI v2 to v3
Refer to the v3 DateRangePicker documentation 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:
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:
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) |
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
import { DateRangePicker } from "@heroui/react";
<DateRangePicker label="Trip dates" />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
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}
/>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
<DateRangePicker
label="Trip dates"
description="Select your travel period"
errorMessage="End date must be after start date"
isInvalid={isInvalid}
/>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
import { Icon } from "@iconify/react";
<DateRangePicker
label="Trip dates"
selectorIcon={<Icon icon="gravity-ui:calendar" />}
/>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
<DateRangePicker
label="Trip dates"
visibleMonths={2}
showMonthAndYearPickers
/><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
<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
<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
- Component Structure: Single component → composition of
DateRangePicker,DateField,RangeCalendar, andLabel - Dual Inputs: Built-in start/end inputs → two
DateField.Inputwithslot="start"andslot="end" - Separator: Built-in →
DateRangePicker.RangeSeparatorcomponent - Label:
labelprop →Labelcomponent - Description/Error: Props →
DescriptionandFieldErrorcomponents - Calendar: Built-in → compose
RangeCalendarwith its own compound components insideDateRangePicker.Popover - Selector Icon:
selectorIconprop → pass children toDateRangePicker.TriggerIndicator - Year Picker:
showMonthAndYearPickersprop → useRangeCalendar.YearPickerTriggerandRangeCalendar.YearPickerGrid - Styling Props Removed:
variant,color,size,radius→ use Tailwind CSS orDateField.Groupvariant - ClassNames Removed: Use
classNameon individual compound components