ProComponents, templates & AI tooling
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.TriggerSelect.ValueSelect.IndicatorSelect.Popover)与用于菜单项的 ListBox

2. 菜单项组件

v2: SelectItemSelectSection
v3: ListBox.ItemListBox.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
isClearable手动实现清除按钮
renderValue使用 Select.Value 的渲染 prop
labelPlacement标签始终在外部
isRequiredisRequired仍然可用
disabledKeysdisabledKeys仍然可用
isOpenisOpen新增:受控地控制 Popover 的打开状态
defaultOpendefaultOpen新增:非受控的默认打开状态
onOpenChangeonOpenChange新增:打开状态变化时触发
selectionModeselectionMode仍然可用
disableAnimation已移除(动画机制已不同)
popoverPropslistboxPropsscrollShadowProps直接在 Select.PopoverListBox 等组件上传入 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.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: selectedKeysSet<Key> 或数组
  • v3: 单选时 valueKey | null,多选时为 Key[]

清除按钮

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

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

总结

  1. 组件结构:必须使用复合组件(Select.TriggerSelect.ValueSelect.IndicatorSelect.Popover)。
  2. 菜单项组件SelectItemListBox.ItemSelectSectionListBox.Section(分组标题用 Header,分组之间用 Separator)。
  3. 标签 / 描述 / 错误:使用独立组件,而不是对应 prop。
  4. 选择相关 propselectedKeys / onSelectionChangevalue / onChange
  5. 受控的打开状态:新增 isOpendefaultOpenonOpenChange 用于控制 Popover 的打开状态。
  6. 禁用选项:仍支持 disabledKeys 以禁用指定菜单项。
  7. 必填:仍支持 isRequired 标记字段为必填。
  8. 样式variant 仅保留 primary | secondarycolorsizeradius 已移除,请用 Tailwind CSS 扩展。
  9. classNames 已移除:在各子组件上使用 className prop。
  10. 内容类 prop 已移除startContentendContent 需通过自定义触发器实现。
  11. 清除按钮isClearable 已移除,需手动实现。
  12. 自定义值:用 Select.Value 的渲染 prop 替代 renderValue

本页目录