---
title: DatePicker
description: DatePicker 从 HeroUI v2 到 v3 的迁移指南。
---

<Callout type="info">
  完整的 API 参考、样式指南与高级示例请参阅 [v3 DatePicker 文档](/docs/react/components/date-picker)。本指南只关注从 HeroUI v2 的迁移。
</Callout>

## 结构变化

在 v2 中，`DatePicker` 是一个自包含组件，通过 props 处理标签、描述、日历和时间输入：

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

export default function App() {
  return (
    <DatePicker label="Birth date" />
  );
}
```

在 v3 中，DatePicker 改用组合优先的 API，你需要显式组合 `DateField` 与 `Calendar`：

```tsx
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>
  );
}
```

## 主要变化

### 1. 组件结构

**v2：** 单一 `DatePicker` 组件，所有部分（输入、日历、标签、popover）都通过 props 在内部处理  
**v3：** 基于组合的 API，由 `DatePicker`、`DateField`、`Calendar` 和 `Label` 共同组成。每个部分都是独立组件。

### 2. 组成部分

| v3 组件 | 描述 |
| ------- | ---- |
| `DatePicker` | 根容器与状态持有者 |
| `DatePicker.Trigger` | 打开日历 popover 的按钮 |
| `DatePicker.TriggerIndicator` | 日历图标（默认）或自定义指示器 |
| `DatePicker.Popover` | 日历的 popover 包装层 |
| `DateField.Group` | 输入组包装层 |
| `DateField.Input` | 分段日期输入 |
| `DateField.Segment` | 单独的日期片段（月、日、年） |
| `DateField.Suffix` | 触发按钮的后缀 slot |
| `Calendar` | 完整的日历组件（参见 [Calendar 迁移](/docs/react/migration/calendar)） |
| `Label` | 外部 Label 组件 |

### 3. Prop 变更

| v2 prop | v3 等效项 | 说明 |
| ------- | ----------- | ---- |
| `value` | `value` | 保持一致 |
| `defaultValue` | `defaultValue` | 保持一致 |
| `onChange` | `onChange` | 保持一致 |
| `minValue` | `minValue` | 保持一致 |
| `maxValue` | `maxValue` | 保持一致 |
| `isDateUnavailable` | `isDateUnavailable` | 保持一致 |
| `isDisabled` | `isDisabled` | 保持一致 |
| `isReadOnly` | `isReadOnly` | 保持一致 |
| `isRequired` | `isRequired` | 保持一致 |
| `isInvalid` | `isInvalid` | 保持一致 |
| `granularity` | `granularity` | 保持一致 |
| `hourCycle` | `hourCycle` | 保持一致 |
| `hideTimeZone` | `hideTimeZone` | 保持一致 |
| `shouldForceLeadingZeros` | `shouldForceLeadingZeros` | 保持一致 |
| `pageBehavior` | - | 直接设置在 `Calendar` 上 |
| `label` | - | 使用 `Label` 组件 |
| `description` | - | 使用 `Description` 组件 |
| `errorMessage` | - | 使用 `FieldError` 组件 |
| `variant` | - | 使用 `DateField.Group` 的 `variant` prop 或 Tailwind CSS |
| `color` | - | 已移除（请改用 Tailwind CSS） |
| `size` | - | 已移除（请改用 Tailwind CSS） |
| `radius` | - | 已移除（请改用 Tailwind CSS） |
| `labelPlacement` | - | 使用 Tailwind CSS 控制布局 |
| `startContent` | - | 在 `DateField.Group` 内组合 |
| `endContent` | - | 在 `DateField.Group` 内组合 |
| `selectorIcon` | - | 将 children 传给 `DatePicker.TriggerIndicator` |
| `visibleMonths` | - | 使用 `Calendar` 的 `visibleDuration` prop |
| `showMonthAndYearPickers` | - | 使用 `Calendar.YearPickerTrigger` 和 `Calendar.YearPickerGrid` |
| `calendarProps` | - | 直接将 props 传给 `Calendar` |
| `popoverProps` | - | 直接将 props 传给 `DatePicker.Popover` |
| `selectorButtonProps` | - | 直接将 props 传给 `DatePicker.Trigger` |
| `timeInputProps` | - | 单独组合时间输入 |
| `CalendarBottomContent` | - | 将内容放在 `DatePicker.Popover` 内、`Calendar` 之后 |
| `validate` | - | 在外部处理校验 |
| `placeholderValue` | `placeholderValue` | 保持一致 |
| `autoFocus` | `autoFocus` | 保持一致 |
| `disableAnimation` | - | 已移除 |
| `classNames` | - | 在各个组件上使用 `className` |

## 迁移示例

### 基本日期选择器

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    import { DatePicker } from "@heroui/react";

    <DatePicker label="Birth date" />
    ```
  </Tab>
  <Tab value="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>
    ```
  </Tab>
</Tabs>

### 受控状态

<Tabs items={["v2", "v3"]}>
  <Tab value="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}
    />
    ```
  </Tab>
  <Tab value="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>
    ```
  </Tab>
</Tabs>

### 带描述和错误信息

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <DatePicker
      label="Event date"
      description="Choose a future date"
      errorMessage="Date must be in the future"
      isInvalid={isInvalid}
    />
    ```
  </Tab>
  <Tab value="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>
    ```
  </Tab>
</Tabs>

### 自定义选择器图标

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    import { Icon } from "@iconify/react";

    <DatePicker
      label="Date"
      selectorIcon={<Icon icon="gravity-ui:calendar" />}
    />
    ```
  </Tab>
  <Tab value="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>
    ```
  </Tab>
</Tabs>

### 带月份 / 年份选择器和日历底部内容

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <DatePicker
      label="Date"
      showMonthAndYearPickers
      CalendarBottomContent={
        <button onClick={() => setValue(today(getLocalTimeZone()))}>Today</button>
      }
    />
    ```
  </Tab>
  <Tab value="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>
    ```
  </Tab>
</Tabs>

## 样式变化

### v2：`classNames` prop

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

### v3：直接使用 `className` prop

```tsx
<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>
```

## 组件结构

v3 DatePicker 遵循以下结构：

```
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]
```

## 总结

1. **组件结构**：单一组件 → 由 `DatePicker`、`DateField`、`Calendar` 和 `Label` 组合而成
2. **标签**：`label` prop → `Label` 组件
3. **描述 / 错误信息**：props → `Description` 和 `FieldError` 组件
4. **日历**：内置日历 → 在 `DatePicker.Popover` 内组合带有自身复合组件的 `Calendar`
5. **选择器图标**：`selectorIcon` prop → 将 children 传给 `DatePicker.TriggerIndicator`
6. **年份选择器**：`showMonthAndYearPickers` prop → 使用 `Calendar.YearPickerTrigger` 和 `Calendar.YearPickerGrid`
7. **日历底部内容**：`CalendarBottomContent` prop → 将内容放在 `DatePicker.Popover` 内、`Calendar` 之后
8. **已移除样式 prop**：`variant`、`color`、`size`、`radius` → 使用 Tailwind CSS 或 `DateField.Group` 的变体
9. **已移除 classNames**：在各个复合组件上使用 `className`
10. **多个月份**：`visibleMonths` prop → 使用 `Calendar` 的 `visibleDuration` prop
