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
import { DatePicker } from "@heroui/react";
<DatePicker label="Birth date" />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
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}
/>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
<DatePicker
label="Event date"
description="Choose a future date"
errorMessage="Date must be in the future"
isInvalid={isInvalid}
/>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
import { Icon } from "@iconify/react";
<DatePicker
label="Date"
selectorIcon={<Icon icon="gravity-ui:calendar" />}
/>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
<DatePicker
label="Date"
showMonthAndYearPickers
CalendarBottomContent={
<button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
}
/><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