Listbox(列表框)让人从始终可见的列表里选一个或多个选项。它像一个显示多行的原生 select,带键盘导航、键入定位(typeahead)与可选的范围选择。选项应当一直留在视野里时用它;需要弹出触发器时用选择器,需要键入过滤时用组合框。
组合
Listbox
├── ListboxItem
│ └── ListboxItemIndicator
└── ListboxItem
└── ListboxItemIndicator给列表一个无障碍名称,给每个选项一个唯一 value。ListboxItemIndicator 在该选项被选中时显示其内容。也可用字段标签或 aria-labelledby 命名。按钮、搜索框与空消息应放在列表之外:列表内容只应是选项、分组与分隔。
用法
import { Listbox, ListboxItem, ListboxItemIndicator } from "@ui-sdk/react/listbox";
function Example() {
return (
<Listbox aria-label="配料" defaultValue="basil">
<ListboxItem value="basil">
罗勒
<ListboxItemIndicator>✓</ListboxItemIndicator>
</ListboxItem>
<ListboxItem value="olive">
橄榄
<ListboxItemIndicator>✓</ListboxItemIndicator>
</ListboxItem>
</Listbox>
);
}多选与范围选择
加 multiple 选多个选项,值变成数组(单选时为单个值或 null)。默认情况下方向键只移动焦点而不改变多选选择:
| 操作 | 结果 |
|---|---|
点击选项或 Space |
切换该选项 |
Shift + 点击 / 方向键 / Space |
从上次选中项起选一个范围 |
Ctrl/Cmd + Shift + Home / End |
把范围扩展到首项或末项 |
Ctrl/Cmd + A |
选中所有启用项,再按一次清空 |
改变 Shift 范围会保留该范围开始前已选的选项,范围会跳过禁用项。
<Listbox multiple defaultValue={["cheese", "onion"]} aria-label="配料">
{toppings.map((t) => (
<ListboxItem key={t.value} value={t.value}>
{t.label}
<ListboxItemIndicator>✓</ListboxItemIndicator>
</ListboxItem>
))}
</Listbox>激活方式
activation 决定选择是否跟随焦点。单选列表默认 "automatic":方向键、Home、End 与键入移动到哪项就选中哪项。多选列表设 activation="automatic" 后像文件列表:点击或方向键替换选择,Ctrl/Cmd 追加,Ctrl/Cmd + 方向键只移动焦点不选择(触摸与屏幕阅读器按下则切换,因为没有修饰键)。设 activation="manual" 可只浏览不选择,由 Space、Enter 或点击来选中焦点项:
<Listbox activation="manual" aria-label="框架">
{/* 选项 */}
</Listbox>可取消选择与横向排列
单选列表加 deselectable,再次点击已选项即清空选择(否则保持不变)。orientation="horizontal" 改用 Left / Right 移动(RTL 文本反向),loop 控制方向键是否首尾循环(Shift + 方向键从不循环),keepInView 让列表把按键移动到的选项滚动进视野:
<Listbox deselectable defaultValue="astro" aria-label="框架">
{/* 选项 */}
</Listbox>选项即数据与控制器
把选项作为 items 传入即由组件渲染,键入还能找到虚拟化列表尚未渲染的项;分组用 ListboxGroup、ListboxGroupLabel,分隔用 ListboxSeparator(方向键跨组移动并跳过组标签)。对象值用 itemToValue、isItemEqualToValue 处理标识与比较。createListboxController() 创建的控制器提供 getValue / setValue / clear / selectAll / focus,写方法像用户操作那样跑 onValueChange。参见控制器。
样式与状态
用 data-selected 表示选中、data-highlighted 表示焦点;当选择不跟随焦点时,两者可能落在不同选项上。列表带 data-orientation(vertical / horizontal)、data-disabled、data-readonly、data-required;选项带 data-selected、data-highlighted、data-disabled(禁用项仍可聚焦)。指示器继承 data-selected,keepMounted 让它在未选中时也留在页面里以便两态做动画:
[data-slot="listbox-item"][data-highlighted] {
outline: 2px solid var(--color-brand);
outline-offset: -2px;
}
[data-slot="listbox-item"][data-selected] {
background: var(--color-accent);
color: var(--color-brand);
}
[data-slot="listbox"][data-orientation="horizontal"] {
display: flex;
gap: 0.25rem;
}API
根 Listbox 的常用属性:
value/defaultValue(T | T[] | null):受控值 / 初始值,multiple时为数组,null表示未选。onValueChange((value, details) => void):值想变更时调用,details.reason说明来源,details.cancel()拦截。multiple(boolean):允许多选,Shift选范围,Ctrl/Cmd+A全选。activation("automatic" | "manual"):选择是否跟随焦点。deselectable(boolean):单选时再次点击已选项是否清空。orientation("horizontal" | "vertical")、loop(boolean)、keepInView(boolean)。items(ListData<T>)、itemToValue、isItemEqualToValue、onHighlight。name/required/disabled/readOnly:表单提交与可用性。controller(object):用createListboxController()创建。
ListboxItem 接受 value、label(键入定位匹配的文本)、disabled、index;ListboxItemIndicator 接受 keepMounted。
键盘与无障碍
遵循 WAI-ARIA listbox 模式,多选支持其两种键盘模型。选项获得真实焦点,整个列表是一个 roving tab stop:Tab 回到上次聚焦的选项,否则落到首个选中或启用项;因为选项获得真实 DOM 焦点,focus-visible 直接可用。根会把焦点转进选项(包括点击字段标签或列表内边距时),Escape 仍留给外层对话框。
- 单选:方向键移动焦点且选择跟随,
Home/End跳到首尾,键入定位并选中以该字符开头的项;Escape不改变选择。 - 多选(默认):点击与
Space切换,方向键只移动焦点,Shift选范围,Ctrl/Cmd+A全选再清空,键入只移动焦点。 - 每个选项都向屏幕阅读器表明是否选中;禁用项不可选,方向键、键入与范围都跳过它。