Pro组件、模板与 AI 工具
HeroUI
27.7k

Select

Select 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 Select 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,Select 通过 prop 构成简单结构:

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 渲染菜单项:

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.ClearButton、Select.Indicator、Select.Popover)与用于菜单项的 ListBox

2. 菜单项组件

v2: SelectItem、SelectSection
v3: ListBox.Item、ListBox.Section(分组标题使用 Header,分组之间使用 Separator)

3. Prop 变更

v2 propv3 位置说明
selectedKeysvalue从 Set / 数组改为单个值或数组
onSelectionChangeonChange已重命名的事件处理函数
defaultSelectedKeysdefaultValue已重命名的 prop
label—使用 Label 组件
description—使用 Description 组件
errorMessage—使用 FieldError 组件
variantvariant仅保留 primary | secondary
color—已移除(请使用 Tailwind CSS)
size—已移除(请使用 Tailwind CSS)
radius—已移除(请使用 Tailwind CSS)
classNames—在各子组件上使用 className prop
startContent—直接自定义 Select.Trigger
endContent—直接自定义 Select.Trigger
selectorIcon—自定义 Select.Indicator 的 children
isClearableSelect.ClearButton内置子组件;不要在 Select.Trigger 内嵌套 <button>
renderValue—使用 Select.Value 的渲染 prop
labelPlacement—标签始终在外部
isRequiredisRequired仍然可用
disabledKeysdisabledKeys仍然可用
isOpenisOpen新增:受控地控制 Popover 的打开状态
defaultOpendefaultOpen新增:非受控的默认打开状态
onOpenChangeonOpenChange新增:打开状态变化时触发
selectionModeselectionMode仍然可用
disableAnimation—已移除(动画机制已不同)
popoverProps、listboxProps、scrollShadowProps—直接在 Select.Popover、ListBox 等组件上传入 prop

迁移示例

选择

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

表单校验

{/* 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>
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>

带分组

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

受控的打开状态

{/* v2 不支持受控的打开状态 */}
<Select label="Select animal">
  <SelectItem key="cat">Cat</SelectItem>
  <SelectItem key="dog">Dog</SelectItem>
</Select>
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>

禁用选项

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

必填

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

<Select isRequired label="Select animal">
  <SelectItem key="cat">Cat</SelectItem>
  <SelectItem key="dog">Dog</SelectItem>
</Select>
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>

自定义指示器

<Select selectorIcon={<CustomIcon />} label="Select animal">
  <SelectItem key="cat">Cat</SelectItem>
</Select>
<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>

组件剖析

v3 Select 的结构如下:

Select (Root)
  ├── Label (optional)
  ├── Select.Trigger
  │   ├── Select.Value
  │   ├── Select.ClearButton(可选)
  │   └── 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 已移除。请在 Select.Trigger 内使用 Select.ClearButton。不要在触发器内嵌套 <button> — Select.Trigger 本身已经是 button。

清除控件面向指针操作;触发器同时支持按 Backspace 或 Delete 清除,以便键盘与屏幕阅读器用户使用。

<Select value={value} onChange={setValue}>
  <Select.Trigger>
    <Select.Value />
    <Select.ClearButton />
    <Select.Indicator />
  </Select.Trigger>
  {/* ... */}
</Select>

总结

  1. 组件结构:必须使用复合组件(Select.Trigger、Select.Value、Select.ClearButton、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 已移除,请在 Select.Trigger 内使用 Select.ClearButton(不要嵌套 <button>)。
  12. 自定义值:用 Select.Value 的渲染 prop 替代 renderValue。

本页目录