DatePicker
Migration guide for DatePicker from HeroUI v2 to v3
Refer to the v3 DatePicker documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Structure Changes
In v2, DatePicker was a single self-contained component with props for label, description, calendar, and time input:
import { DatePicker } from "@heroui/react";
export default function App() {
return (
<DatePicker label="Birth date" />
);
}
In v3, DatePicker uses a composition-first API where you compose DateField and Calendar explicitly:
import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
export default function App() {
return (
<DatePicker>
<Label>Birth date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose 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>
</DatePicker.Popover>
</DatePicker>
);
}
Key Changes
1. Component Structure
v2: Single DatePicker component with all parts (input, calendar, label, popover) handled internally via props
v3: Composition-based API with DatePicker, DateField, Calendar, and Label composed together. Each part is a separate component.
2. Composition Parts
| v3 Component | Description |
|---|---|
DatePicker | Root container and state owner |
DatePicker.Trigger | Button that opens the calendar popover |
DatePicker.TriggerIndicator | Calendar icon (default) or custom indicator |
DatePicker.Popover | Popover wrapper for the calendar |
DateField.Group | Input group wrapper |
DateField.Input | Segmented date input |
DateField.Segment | Individual date segment (month, day, year) |
DateField.Suffix | Suffix slot for the trigger button |
Calendar | Full calendar component (see Calendar 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 |
isDateUnavailable | isDateUnavailable | Same |
isDisabled | isDisabled | Same |
isReadOnly | isReadOnly | Same |
isRequired | isRequired | Same |
isInvalid | isInvalid | Same |
granularity | granularity | Same |
hourCycle | hourCycle | Same |
hideTimeZone | hideTimeZone | Same |
shouldForceLeadingZeros | shouldForceLeadingZeros | Same |
pageBehavior | - | Set on Calendar 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 |
startContent | - | Compose inside DateField.Group |
endContent | - | Compose inside DateField.Group |
selectorIcon | - | Pass children to DatePicker.TriggerIndicator |
visibleMonths | - | Use Calendar visibleDuration prop |
showMonthAndYearPickers | - | Use Calendar.YearPickerTrigger and Calendar.YearPickerGrid |
calendarProps | - | Pass props directly to Calendar |
popoverProps | - | Pass props directly to DatePicker.Popover |
selectorButtonProps | - | Pass props directly to DatePicker.Trigger |
timeInputProps | - | Compose time input separately |
CalendarBottomContent | - | Place content inside DatePicker.Popover after Calendar |
validate | - | Handle validation externally |
placeholderValue | placeholderValue | Same |
autoFocus | autoFocus | Same |
disableAnimation | - | Removed |
classNames | - | Use className on individual components |
Migration Examples
Basic DatePicker
v2
```tsx
import { DatePicker } from "@heroui/react";
<DatePicker label="Birth date" />
```
v3
```tsx
import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
<DatePicker>
<Label>Birth date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose 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>
</DatePicker.Popover>
</DatePicker>
```
Controlled State
v2
```tsx
import { useState } from "react";
import { DatePicker } from "@heroui/react";
import { parseDate } from "@internationalized/date";
const [value, setValue] = useState(parseDate("2024-03-07"));
<DatePicker
label="Date"
value={value}
onChange={setValue}
/>
```
v3
```tsx
import { useState } from "react";
import { DatePicker, DateField, Calendar, Label } from "@heroui/react";
import { parseDate } from "@internationalized/date";
const [value, setValue] = useState(parseDate("2024-03-07"));
<DatePicker value={value} onChange={setValue}>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose 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>
</DatePicker.Popover>
</DatePicker>
```
With Description and Error Message
v2
```tsx
<DatePicker
label="Event date"
description="Choose a future date"
errorMessage="Date must be in the future"
isInvalid={isInvalid}
/>
```
v3
```tsx
import { DatePicker, DateField, Calendar, Label, Description, FieldError } from "@heroui/react";
<DatePicker isInvalid={isInvalid}>
<Label>Event date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<Description>Choose a future date</Description>
<FieldError>Date must be in the future</FieldError>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>
```
Custom Selector Icon
v2
```tsx
import { Icon } from "@iconify/react";
<DatePicker
label="Date"
selectorIcon={<Icon icon="gravity-ui:calendar" />}
/>
```
v3
```tsx
import { Icon } from "@iconify/react";
<DatePicker>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator>
<Icon icon="gravity-ui:calendar" />
</DatePicker.TriggerIndicator>
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose date">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>
```
With Month/Year Pickers and Calendar Bottom Content
v2
```tsx
<DatePicker
label="Date"
showMonthAndYearPickers
CalendarBottomContent={
<button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
}
/>
```
v3
```tsx
<DatePicker>
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger>
<DatePicker.TriggerIndicator />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover>
<Calendar aria-label="Choose 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>
<button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
</DatePicker.Popover>
</DatePicker>
```
Styling Changes
v2: classNames Prop
<DatePicker
classNames={{
base: "custom-base",
selectorButton: "custom-trigger",
selectorIcon: "custom-icon",
popoverContent: "custom-popover",
calendar: "custom-calendar",
}}
/>
v3: Direct className Props
<DatePicker className="custom-base">
<Label>Date</Label>
<DateField.Group>
<DateField.Input>
{(segment) => <DateField.Segment segment={segment} />}
</DateField.Input>
<DateField.Suffix>
<DatePicker.Trigger className="custom-trigger">
<DatePicker.TriggerIndicator className="custom-icon" />
</DatePicker.Trigger>
</DateField.Suffix>
</DateField.Group>
<DatePicker.Popover className="custom-popover">
<Calendar aria-label="Choose date" className="custom-calendar">
{/* Calendar compound components */}
</Calendar>
</DatePicker.Popover>
</DatePicker>
Component Anatomy
The v3 DatePicker follows this structure:
DatePicker (Root)
├── Label
├── DateField.Group
│ ├── DateField.Input
│ │ └── DateField.Segment (render prop per segment)
│ └── DateField.Suffix
│ └── DatePicker.Trigger
│ └── DatePicker.TriggerIndicator
├── Description (optional)
├── FieldError (optional)
└── DatePicker.Popover
├── Calendar (see Calendar migration guide)
└── [Custom bottom content]
Summary
- Component Structure: Single component → composition of
DatePicker,DateField,Calendar, andLabel - Label:
labelprop →Labelcomponent - Description/Error: Props →
DescriptionandFieldErrorcomponents - Calendar: Built-in → compose
Calendarwith its own compound components insideDatePicker.Popover - Selector Icon:
selectorIconprop → pass children toDatePicker.TriggerIndicator - Year Picker:
showMonthAndYearPickersprop → useCalendar.YearPickerTriggerandCalendar.YearPickerGrid - Calendar Bottom Content:
CalendarBottomContentprop → place content insideDatePicker.PopoverafterCalendar - Styling Props Removed:
variant,color,size,radius→ use Tailwind CSS orDateField.Groupvariant - ClassNames Removed: Use
classNameon individual compound components - Multiple Months:
visibleMonthsprop → useCalendarvisibleDurationprop
Local source: vendor/en/react/migration/(components)/date-picker.mdx
Generated from the pinned HeroUI v3 commit with internal links and previews adapted for JGuang.