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

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

## 结构变化

在 v2 中，Select 通过 prop 构成简单结构：

```tsx
import { Select, SelectItem } from "@heroui/react";

export default function App() {
  return (
    <Select label="Select animal" placeholder="Choose one">
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
    </Select>
  );
}
```

在 v3 中，Select 需要使用复合组件，并通过 `ListBox` 渲染菜单项：

```tsx
import { Select, Label, ListBox } from "@heroui/react";

export default function App() {
  return (
    <Select placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
  );
}
```

## 关键变化

### 1. 组件结构

**v2：** 简单的 Select，子节点为 `SelectItem`  
**v3：** 复合组件（`Select.Trigger`、`Select.Value`、`Select.Indicator`、`Select.Popover`）与用于菜单项的 `ListBox`

### 2. 菜单项组件

**v2：** `SelectItem`、`SelectSection`  
**v3：** `ListBox.Item`、`ListBox.Section`（分组标题使用 `Header`，分组之间使用 `Separator`）

### 3. Prop 变更

| v2 prop | v3 位置 | 说明 |
| ------- | ------- | ---- |
| `selectedKeys` | `value` | 从 Set / 数组改为单个值或数组 |
| `onSelectionChange` | `onChange` | 已重命名的事件处理函数 |
| `defaultSelectedKeys` | `defaultValue` | 已重命名的 prop |
| `label` | — | 使用 `Label` 组件 |
| `description` | — | 使用 `Description` 组件 |
| `errorMessage` | — | 使用 `FieldError` 组件 |
| `variant` | `variant` | 仅保留 `primary` \| `secondary` |
| `color` | — | 已移除（请使用 Tailwind CSS） |
| `size` | — | 已移除（请使用 Tailwind CSS） |
| `radius` | — | 已移除（请使用 Tailwind CSS） |
| `classNames` | — | 在各子组件上使用 `className` prop |
| `startContent` | — | 直接自定义 `Select.Trigger` |
| `endContent` | — | 直接自定义 `Select.Trigger` |
| `selectorIcon` | — | 自定义 `Select.Indicator` 的 children |
| `isClearable` | — | 手动实现清除按钮 |
| `renderValue` | — | 使用 `Select.Value` 的渲染 prop |
| `labelPlacement` | — | 标签始终在外部 |
| `isRequired` | `isRequired` | 仍然可用 |
| `disabledKeys` | `disabledKeys` | 仍然可用 |
| `isOpen` | `isOpen` | 新增：受控地控制 Popover 的打开状态 |
| `defaultOpen` | `defaultOpen` | 新增：非受控的默认打开状态 |
| `onOpenChange` | `onOpenChange` | 新增：打开状态变化时触发 |
| `selectionMode` | `selectionMode` | 仍然可用 |
| `disableAnimation` | — | 已移除（动画机制已不同） |
| `popoverProps`、`listboxProps`、`scrollShadowProps` | — | 直接在 `Select.Popover`、`ListBox` 等组件上传入 prop |

## 迁移示例

### 选择

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

    {/* Single selection */}
    const [singleValue, setSingleValue] = useState(new Set([]));
    <Select
      selectedKeys={singleValue}
      onSelectionChange={setSingleValue}
      label="Select animal"
    >
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
    </Select>
    
    {/* Multiple selection */}
    const [multiValue, setMultiValue] = useState(new Set([]));
    <Select
      selectionMode="multiple"
      selectedKeys={multiValue}
      onSelectionChange={setMultiValue}
      label="Select animals"
    >
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { useState } from "react";
    import type { Key } from "@heroui/react";

    {/* Single selection */}
    const [singleValue, setSingleValue] = useState<Key | null>(null);
    <Select value={singleValue} onChange={setSingleValue} placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    
    {/* Multiple selection */}
    const [multiValue, setMultiValue] = useState<Key[]>([]);
    <Select
      selectionMode="multiple"
      value={multiValue}
      onChange={setMultiValue}
      placeholder="Choose multiple"
    >
      <Label>Select animals</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

### 表单校验

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* With description */}
    <Select
      label="Select animal"
      description="Choose your favorite"
      placeholder="Choose one"
    >
      <SelectItem key="cat">Cat</SelectItem>
    </Select>
    
    {/* With validation */}
    <Select
      isInvalid
      errorMessage="Please select an option"
      label="Select animal"
    >
      <SelectItem key="cat">Cat</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { Label, Description, FieldError } from "@heroui/react";

    {/* With description */}
    <Select placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Description>Choose your favorite</Description>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    
    {/* With validation */}
    <Select isInvalid placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
      <FieldError>Please select an option</FieldError>
    </Select>
    ```
  </Tab>
</Tabs>

### 带分组

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

    <Select label="Select animal">
      <SelectSection title="Mammals">
        <SelectItem key="cat">Cat</SelectItem>
        <SelectItem key="dog">Dog</SelectItem>
      </SelectSection>
      <SelectSection title="Birds">
        <SelectItem key="eagle">Eagle</SelectItem>
        <SelectItem key="parrot">Parrot</SelectItem>
      </SelectSection>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { Select, Label, ListBox, Header, Separator } from "@heroui/react";

    <Select placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Section>
            <Header>Mammals</Header>
            <ListBox.Item id="cat" textValue="Cat">
              Cat
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="dog" textValue="Dog">
              Dog
              <ListBox.ItemIndicator />
            </ListBox.Item>
          </ListBox.Section>
          <Separator />
          <ListBox.Section>
            <Header>Birds</Header>
            <ListBox.Item id="eagle" textValue="Eagle">
              Eagle
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="parrot" textValue="Parrot">
              Parrot
              <ListBox.ItemIndicator />
            </ListBox.Item>
          </ListBox.Section>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

### 受控的打开状态

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* v2 不支持受控的打开状态 */}
    <Select label="Select animal">
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { useState } from "react";
    import { Select, Label, ListBox } from "@heroui/react";

    const [isOpen, setIsOpen] = useState(false);

    <Select isOpen={isOpen} onOpenChange={setIsOpen} placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

### 禁用选项

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

    <Select label="Select animal" disabledKeys={["dog"]}>
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
      <SelectItem key="parrot">Parrot</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { Select, Label, ListBox } from "@heroui/react";

    <Select disabledKeys={["dog"]} placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="parrot" textValue="Parrot">
            Parrot
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

### 必填

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

    <Select isRequired label="Select animal">
      <SelectItem key="cat">Cat</SelectItem>
      <SelectItem key="dog">Dog</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { Select, Label, ListBox } from "@heroui/react";

    <Select isRequired placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator />
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
          <ListBox.Item id="dog" textValue="Dog">
            Dog
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

### 自定义指示器

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <Select selectorIcon={<CustomIcon />} label="Select animal">
      <SelectItem key="cat">Cat</SelectItem>
    </Select>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    <Select placeholder="Choose one">
      <Label>Select animal</Label>
      <Select.Trigger>
        <Select.Value />
        <Select.Indicator>
          <CustomIcon />
        </Select.Indicator>
      </Select.Trigger>
      <Select.Popover>
        <ListBox>
          <ListBox.Item id="cat" textValue="Cat">
            Cat
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Select.Popover>
    </Select>
    ```
  </Tab>
</Tabs>

## 组件剖析

v3 Select 的结构如下：

```
Select (Root)
  ├── Label (optional)
  ├── Select.Trigger
  │   ├── Select.Value
  │   └── Select.Indicator
  ├── Description (optional)
  ├── Select.Popover
  │   └── ListBox
  │       ├── ListBox.Item
  │       │   ├── Label (optional)
  │       │   ├── Description (optional)
  │       │   └── ListBox.ItemIndicator
  │       ├── ListBox.Section (optional)
  │       │   ├── Header (section title)
  │       │   └── ListBox.Item
  │       └── Separator (optional, between sections)
  └── FieldError (optional)
```

## 重要说明

### 菜单项标识

- **v2：** React 的 `key` 同时用于列表协调与选择时的项标识。  
- **v3：** 在 `ListBox.Item` 上使用 `id`（状态 / 焦点）与 `textValue`（无障碍）；列表协调仍使用 React 的 `key`。

### 选择值的类型

- **v2：** `selectedKeys` 为 `Set<Key>` 或数组  
- **v3：** 单选时 `value` 为 `Key | null`，多选时为 `Key[]`

### 清除按钮

`isClearable` prop 已移除。若要实现清除按钮：

```tsx
<Select value={value} onChange={setValue}>
  <Select.Trigger>
    <Select.Value />
    {value && (
      <button onClick={() => setValue(null)}>Clear</button>
    )}
    <Select.Indicator />
  </Select.Trigger>
  {/* ... */}
</Select>
```

## 总结

1. **组件结构**：必须使用复合组件（`Select.Trigger`、`Select.Value`、`Select.Indicator`、`Select.Popover`）。  
2. **菜单项组件**：`SelectItem` → `ListBox.Item`，`SelectSection` → `ListBox.Section`（分组标题用 `Header`，分组之间用 `Separator`）。  
3. **标签 / 描述 / 错误**：使用独立组件，而不是对应 prop。  
4. **选择相关 prop**：`selectedKeys` / `onSelectionChange` → `value` / `onChange`。  
5. **受控的打开状态**：新增 `isOpen`、`defaultOpen`、`onOpenChange` 用于控制 Popover 的打开状态。  
6. **禁用选项**：仍支持 `disabledKeys` 以禁用指定菜单项。  
7. **必填**：仍支持 `isRequired` 标记字段为必填。  
8. **样式**：`variant` 仅保留 `primary` \| `secondary`；`color`、`size`、`radius` 已移除，请用 Tailwind CSS 扩展。  
9. **classNames 已移除**：在各子组件上使用 `className` prop。  
10. **内容类 prop 已移除**：`startContent`、`endContent` 需通过自定义触发器实现。  
11. **清除按钮**：`isClearable` 已移除，需手动实现。  
12. **自定义值**：用 `Select.Value` 的渲染 prop 替代 `renderValue`。
