一个 React 选择器,采用 Liquid Glass 触发器,支持单选或多选、分组选项与五种尺寸。
Select 内部负责标签、触发器、玻璃表面、当前值、箭头、Portal、玻璃浮层与选项。选项通过 items 数组传入,同一份数据同时驱动触发器文字和浮层列表;选项可以是平铺的,也可以按组传入,设置 multiple 后值会变为数组。
使用 label 与 description 提供可见字段文本。没有可见标签时,请通过 aria-label 或 aria-labelledby 为触发器提供可访问名称。设置 name 后,选中值会通过隐藏的原生输入提交。
示例展示带标签、描述、占位文本与禁用选项的字段,并通过 defaultValue 选中第一个选项。
设置 multiple 后可以选择多个选项。此时值变为数组,每次选中后浮层保持打开,列表会暴露 aria-multiselectable。当触发器需要显示摘要而不是逐项列出标签时,使用 renderValue;它会覆盖 placeholder,因此空状态文案需要由函数自行返回。
在 items 中传入 { label, items } 形式的分组。每组会渲染标题,并作为带标签的分组被播报;触发器仍会从展平后的选项中解析标签。
Value 不限于字符串。可以把对象作为选项值,并通过 itemToStringValue 控制表单提交、通过 isItemEqualToValue 匹配引用不同但内容相等的对象。renderValue 可以在触发器中展示完整对象。
支持 xs / sm / md / lg / xl,默认 md。触发器高度、内边距、文字与箭头会一起缩放。
value 与 onValueChange 控制选中值。把受控 value 设为 null 可以清空选择;组件本身不渲染清除控件,因此示例在字段旁提供了独立的清除操作。
Selected plan: team
设置 name 后,选中值会通过隐藏的原生输入提交。required 会参与原生表单校验,form 可将字段关联到文档其他位置的表单。
Select 遵循 Base UI 选择器模式:触发器是 combobox,暴露 aria-haspopup="listbox"、aria-expanded 与 aria-controls;内部列表是 listbox;每个选项都是 option。ArrowDown 与 ArrowUp 在选项之间移动,typeahead 会匹配标签,Enter 选中高亮项。Escape 关闭浮层并把焦点返回触发器。
设置 multiple 后,列表会暴露 aria-multiselectable="true",每个选项暴露 aria-selected,激活选项会切换选中状态而不会关闭浮层。分组选项会渲染在由标题命名的 group 中。
可见的 label 为触发器命名,description 通过 aria-describedby 关联。没有可见标签时,请使用 aria-label 或 aria-labelledby。禁用的选项仍会被播报,但无法选中。必须选择值时请设置 required;共享表单模式见可访问性指南。
Select
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
items | readonly (SelectItemProps | SelectItemGroupProps)[] | — | 选择器中的选项,可按组传入。必填。 |
label | ReactNode | — | 与触发器关联的可见标签。 |
description | ReactNode | — | 触发器下方的辅助说明,通过 aria-describedby 关联。 |
placeholder | ReactNode | — | 未选择时显示的文字。 |
renderValue | (value) => ReactNode | — | 格式化触发器中的当前值。参数为 Value | null,多选时为 readonly Value[]。会覆盖 placeholder。 |
multiple | boolean | false | 允许选择多个选项;此时 value、defaultValue 与 onValueChange 使用数组。 |
value | Value | null | — | 受控选中值。多选时使用 readonly Value[] | null。 |
defaultValue | Value | null | — | 非受控时的初始值。多选时使用 readonly Value[] | null。 |
onValueChange | (value, details) => void | — | 选中值变化时调用。单选接收 Value | null,多选接收 Value[]。 |
open | boolean | — | 受控的浮层显示状态。 |
defaultOpen | boolean | false | 非受控时的初始浮层状态。 |
onOpenChange | (open: boolean, details) => void | — | 浮层打开或关闭时调用。 |
onOpenChangeComplete | (open: boolean) => void | — | 打开或关闭动画结束后调用。 |
modal | boolean | true | 浮层打开时锁定外部交互。 |
readOnly | boolean | false | 禁止修改当前值。 |
disabled | boolean | false | 禁用触发器与选项。 |
highlightItemOnHover | boolean | true | 指针移动时高亮选项。设为 false 后可单独为 :hover 设置样式,与 data-highlighted 区分。 |
itemToStringLabel | (itemValue: Value) => string | — | 将对象值转换为触发器中显示的文本。 |
itemToStringValue | (itemValue: Value) => string | — | 将对象值转换为表单提交的字符串。 |
isItemEqualToValue | (itemValue: Value, value: Value) => boolean | Object.is | 自定义相等判断,用于匹配引用不同但内容相等的对象值。 |
name | string | — | 隐藏输入框的表单字段名。多选时每个选中值提交一个输入。 |
form | string | — | 隐藏输入框所属的表单。 |
autoComplete | string | — | 传给隐藏输入框的自动填充提示。 |
required | boolean | false | 表单提交前必须选择值。 |
id | string | — | 选择器各部分的基础 id。 |
actionsRef | RefObject<Select.Root.Actions | null> | — | 命令式的 close 与 unmount 操作。 |
inputRef | Ref<HTMLInputElement> | — | 指向隐藏输入框的 ref。 |
aria-label | string | — | 没有可见标签时触发器的可访问名称。 |
aria-labelledby | string | — | 从其他元素为触发器命名。 |
aria-describedby | string | — | 应用于触发器的额外描述 ID。 |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | 控制触发器高度、内边距、文字与图标尺寸。 |
side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "bottom" | 首选方位。 |
align | "start" | "center" | "end" | "center" | 相对触发器的对齐方式。 |
sideOffset | number | OffsetFunction | 6 | 触发器与浮层的像素距离。 |
alignOffset | number | OffsetFunction | 0 | 沿对齐轴的像素偏移。 |
collisionPadding | Padding | 每边 5px | 浮层与碰撞边界之间的留白。 |
alignItemWithTrigger | boolean | false | 让浮层与触发器重叠,使选中项与触发器文字对齐。 |
className | string | — | 合并到字段根元素的 Tailwind 类名。 |
style | CSSProperties | — | 内联样式,合并到与 className 相同的元素。 |
ui | { root?: ClassValue; label?: ClassValue; trigger?: ClassValue; glass?: ClassValue; overlay?: ClassValue; value?: ClassValue; icon?: ClassValue; description?: ClassValue; positioner?: ClassValue; popup?: ClassValue; popupGlass?: ClassValue; popupOverlay?: ClassValue; list?: ClassValue; group?: ClassValue; groupLabel?: ClassValue; item?: ClassValue; itemIndicator?: ClassValue; itemText?: ClassValue } | — | 按 data-slot 组织的内部槽位类名。 |
SelectItemProps
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | any | — | 提交并传给 onValueChange 的值。支持任意类型。必填。 |
label | ReactNode | — | 选项与触发器中的可见内容。必填。 |
textValue | string | — | 键盘 typeahead 使用的纯文本标签;字符串标签会自动使用。 |
disabled | boolean | false | 禁用该选项。 |
SelectItemGroupProps
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | ReactNode | — | 渲染在分组上方的可见标题。必填。 |
items | readonly SelectItemProps[] | — | 分组中的选项。必填。 |
ui 的键对应下方列出的 data-slot 值。其余 Base UI Select.Root 属性都会原样透传。
| 属性 | 说明 |
|---|---|
data-open | 选择器打开时存在于浮层上。 |
data-closed | 浮层关闭或正在关闭时存在。 |
data-placeholder | 未选择任何选项时存在于当前值上。 |
data-selected | 选中项及其指示器上存在。 |
data-highlighted | 键盘或指针高亮该选项时存在。 |
data-disabled | 选择器或选项禁用时存在。 |
data-readonly | 选择器只读时存在。 |
data-popup-open | 浮层打开时存在于触发器上。 |
data-side | 浮层的实际方位。 |
data-align | 实际对齐方式(start、center 或 end)。 |
data-starting-style | 浮层开始进入动画时存在。 |
data-ending-style | 浮层正在退出动画时存在。 |
aria-multiselectable | 设置 multiple 时,列表上为 true。 |
data-slot | 标记内部结构(root、label、trigger、glass、overlay、value、icon、description、positioner、popup、popup-glass、popup-overlay、list、group、group-label、item、item-indicator、item-text)。 |
组件使用共享玻璃材质。可以在父级调整 --ui-glass-* token,用 className 控制布局,并通过 ui 按 data-slot 调整内部槽位。组件级数值位于 src/theme/select.ts。Token 分层与预设见主题指南。