Listbox
Listbox 从 HeroUI v2 到 v3 的迁移指南。
完整的 API 参考、样式指南与高级示例请参阅 v3 ListBox 文档。本指南只关注从 HeroUI v2 的迁移。
结构变化
v2:独立组件
在 v2 中,Listbox 使用彼此独立的组件:
import { Listbox, ListboxItem, ListboxSection } from "@heroui/react";
export default function App() {
return (
<Listbox>
<ListboxItem key="1">Item 1</ListboxItem>
</Listbox>
);
}v3:复合组件
在 v3 中,ListBox 使用复合组件:
import { ListBox, Label } from "@heroui/react";
export default function App() {
return (
<ListBox>
<ListBox.Item id="1" textValue="Item 1">
<Label>Item 1</Label>
</ListBox.Item>
</ListBox>
);
}主要变化
1. 组件命名
v2: Listbox、ListboxItem、ListboxSection
v3: ListBox、ListBox.Item、ListBox.Section
2. 条目标识
v2: React 的 key 同时用于列表调和与条目身份(选择、焦点)。
v3: 使用 id 管理状态与焦点,使用 textValue 提供无障碍信息(当内容不是纯文本时);列表中仍可为各项保留 React 的 key。
3. Prop 变更
| v2 prop | v3 位置 | 说明 |
|---|---|---|
key(用于状态) | ListBox.Item | 条目身份(状态)请使用 id |
| — | textValue(在 ListBox.Item 上) | 用于无障碍(输入预判) |
variant、color | ListBox 或 ListBox.Item 上的 variant | 精简为 "default" | "danger"(不再有单独的 color prop) |
onAction | ListBox | 按下条目时触发,签名为 (key: Key) => void |
disabledKeys | ListBox | 与 v2 相同——一组键,对应应为非交互的条目 |
startContent、endContent | — | 请在条目内容中手动放置图标 |
description | Description | 使用 Description 组件 |
title(在 Section 上) | Header | 使用 Header 组件 |
topContent、bottomContent | — | 已移除(请自行处理) |
itemClasses、classNames | — | 在各部分上使用 className |
hideSelectedIcon | — | 不渲染 ListBox.ItemIndicator |
disableAnimation | — | 已移除 |
isVirtualized、virtualization | React Aria Virtualizer | 使用 React Aria 的 <Virtualizer> 包装(见下方示例) |
selectedKeys | ListBox | 与 v2 相同(使用 Selection 类型的 Set) |
迁移示例
选择
import { useState } from "react";
{/* Single selection */}
const [singleSelected, setSingleSelected] = useState(new Set(["text"]));
<Listbox
selectedKeys={singleSelected}
selectionMode="single"
onSelectionChange={setSingleSelected}
>
<ListboxItem key="text">Text</ListboxItem>
<ListboxItem key="number">Number</ListboxItem>
</Listbox>
{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState(new Set(["text"]));
<Listbox
selectedKeys={multiSelected}
selectionMode="multiple"
onSelectionChange={setMultiSelected}
>
<ListboxItem key="text">Text</ListboxItem>
<ListboxItem key="number">Number</ListboxItem>
</Listbox>import { useState } from "react";
import type { Selection } from "@heroui/react";
{/* Single selection */}
const [singleSelected, setSingleSelected] = useState<Selection>(new Set(["text"]));
<ListBox
selectedKeys={singleSelected}
selectionMode="single"
onSelectionChange={setSingleSelected}
>
<ListBox.Item id="text" textValue="Text">
<Label>Text</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="number" textValue="Number">
<Label>Number</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState<Selection>(new Set(["text"]));
<ListBox
selectedKeys={multiSelected}
selectionMode="multiple"
onSelectionChange={setMultiSelected}
>
<ListBox.Item id="text" textValue="Text">
<Label>Text</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="number" textValue="Number">
<Label>Number</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>含描述
<ListboxItem
key="new"
description="Create a new file"
>
New file
</ListboxItem>import { Description, Label } from "@heroui/react";
<ListBox.Item id="new" textValue="New file">
<Label>New file</Label>
<Description>Create a new file</Description>
</ListBox.Item>含图标
<ListboxItem
key="new"
startContent={<AddIcon />}
>
New file
</ListboxItem><ListBox.Item id="new" textValue="New file">
<AddIcon />
<Label>New file</Label>
</ListBox.Item>含分组
<Listbox>
<ListboxSection title="Actions">
<ListboxItem key="new">New file</ListboxItem>
<ListboxItem key="edit">Edit file</ListboxItem>
</ListboxSection>
<ListboxSection title="Danger zone">
<ListboxItem key="delete">Delete</ListboxItem>
</ListboxSection>
</Listbox>import { Header, Label, Separator } from "@heroui/react";
<ListBox>
<ListBox.Section>
<Header>Actions</Header>
<ListBox.Item id="new" textValue="New file">
<Label>New file</Label>
</ListBox.Item>
<ListBox.Item id="edit" textValue="Edit file">
<Label>Edit file</Label>
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Danger zone</Header>
<ListBox.Item id="delete" textValue="Delete" variant="danger">
<Label>Delete</Label>
</ListBox.Item>
</ListBox.Section>
</ListBox>自定义选中指示器
<ListboxItem
key="1"
selectedIcon={<CustomCheckIcon />}
>
Item 1
</ListboxItem><ListBox.Item id="1" textValue="Item 1">
<Label>Item 1</Label>
<ListBox.ItemIndicator>
{({isSelected}) =>
isSelected ? <CustomCheckIcon /> : null
}
</ListBox.ItemIndicator>
</ListBox.Item>variant prop
在 v3 中,ListBox 与 ListBox.Item 都接受 variant prop,取值为 "default"(默认)或 "danger"。在根级 ListBox 上设置 variant 会作用于所有条目;在单个 ListBox.Item 上设置会覆盖该条目的根级值。
{/* Root-level variant — all items inherit "danger" styling */}
<ListBox variant="danger">
<ListBox.Item id="delete" textValue="Delete">
<Label>Delete</Label>
</ListBox.Item>
</ListBox>
{/* Per-item variant */}
<ListBox>
<ListBox.Item id="edit" textValue="Edit">
<Label>Edit</Label>
</ListBox.Item>
<ListBox.Item id="delete" textValue="Delete" variant="danger">
<Label>Delete</Label>
</ListBox.Item>
</ListBox>onAction 事件处理函数
onAction 在条目被按下(点击或 Enter)时触发,参数为该条目的 id(类型为 Key)。
<ListBox onAction={(key) => alert(`Action on ${key}`)}>
<ListBox.Item id="copy" textValue="Copy">
<Label>Copy</Label>
</ListBox.Item>
<ListBox.Item id="paste" textValue="Paste">
<Label>Paste</Label>
</ListBox.Item>
</ListBox>disabledKeys
使用 disabledKeys 将特定条目设为非交互:
<ListBox disabledKeys={new Set(["paste"])}>
<ListBox.Item id="copy" textValue="Copy">
<Label>Copy</Label>
</ListBox.Item>
<ListBox.Item id="paste" textValue="Paste">
<Label>Paste</Label>
</ListBox.Item>
</ListBox>ListBox.Item 的渲染 prop
ListBox.Item 支持渲染 prop,可读取当前交互状态。可用字段包括 isSelected、isFocused、isDisabled、isPressed:
<ListBox selectionMode="single">
<ListBox.Item id="item1" textValue="Item 1">
{({isSelected, isFocused, isDisabled, isPressed}) => (
<>
<Label className={isSelected ? "font-bold" : ""}>Item 1</Label>
{isSelected && <ListBox.ItemIndicator />}
</>
)}
</ListBox.Item>
</ListBox>虚拟化
v3 仍通过 React Aria 的 <Virtualizer> 支持虚拟化。用 Virtualizer 包裹 ListBox 的条目,以高效渲染长列表:
import {Virtualizer} from "react-aria-components";
<ListBox
aria-label="Large list"
items={items}
selectionMode="multiple"
>
<Virtualizer>
{(item) => (
<ListBox.Item id={item.id} textValue={item.name}>
<Label>{item.name}</Label>
</ListBox.Item>
)}
</Virtualizer>
</ListBox>组件剖析
v3 ListBox 的结构如下:
ListBox (Root)
├── ListBox.Item
│ ├── Icon (optional, manual placement)
│ ├── Label (required)
│ ├── Description (optional)
│ └── ListBox.ItemIndicator (optional)
└── ListBox.Section (optional)
├── Header (optional)
└── ListBox.Item总结
- 组件命名:
Listbox→ListBox,ListboxItem→ListBox.Item,ListboxSection→ListBox.Section。 - 条目结构:必须使用
Label、Description、ListBox.ItemIndicator等组件。 - 图标:不再使用
startContent/endContentprop,改为手动排版。 - 分组:使用
Header组件,不再使用 Section 的titleprop。 variantprop:variant与color合并为单一的variant("default"|"danger"),可设在ListBox与ListBox.Item上。onAction:ListBox上新增onAction,用于处理条目按下。disabledKeys:仍在ListBox上支持,用于禁用特定条目。- 渲染 prop:
ListBox.Item通过渲染 prop 提供isSelected、isFocused、isDisabled、isPressed。 - 已移除的内容 props:
topContent、bottomContent——请自行组织布局。 - 虚拟化:仍通过 React Aria
<Virtualizer>支持(替代isVirtualizedprop)。 - 选择类型:使用
Selection类型(Set),而不是数组。