Select
select下拉选择 · Base UI overlay 单选/多选(multiple·string[] 受控·Trigger 平铺 label 超出 +N) + items 自动 label
用法
基础用法
items 提供选项数据,placeholder 作占位。
<Select items={fonts} placeholder="请选择字体">
<SelectTrigger />
<SelectContent>
{fonts.map((f) => (
<SelectItem key={f.value} value={f.value}>
{f.label}
</SelectItem>
))}
</SelectContent>
</Select>默认已选值
非受控写法用 defaultValue 预设选中项。
<Select items={fonts} defaultValue="serif">
<SelectTrigger />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>多选
multiple 下受控值为 string[],Trigger 平铺已选 label(超出 maxDisplay 折叠 +N),选中后浮层保持打开。
const [value, setValue] = useState<string[]>([]);
<Select items={fonts} placeholder="选择多个字体" multiple value={value} onValueChange={setValue}>
<SelectTrigger maxDisplay={2} />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>可清除
clearable 下有值时 hover / 聚焦字段,右侧箭头位浮出清除按钮;点击置空并回传 null(多选回传 [])。
<Select items={fonts} placeholder="请选择字体" clearable defaultValue="serif">
<SelectTrigger />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>可搜索
searchable 切到 Combobox 搜索皮肤:浮层顶部带搜索框,过滤复用 Base UI Combobox(对标 el-select filterable)。
<Select items={fonts} placeholder="请选择字体" searchable clearable>
<SelectTrigger />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>加载态
loading 下 Trigger 图标换 Spinner,浮层只出加载占位(不展示上一轮的陈旧选项)。
<Select items={fonts} placeholder="请选择字体" loading loadingText="加载中">
<SelectTrigger />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>选项分组
SelectGroup + SelectGroupLabel 给选项分段(Base UI 自动建立 aria 关联)。searchable 皮肤下分组会被拍平。
<SelectContent>
<SelectGroup>
<SelectGroupLabel>西文</SelectGroupLabel>
<SelectItem value="sans">无衬线 Sans</SelectItem>
<SelectItem value="serif">衬线 Serif</SelectItem>
</SelectGroup>
</SelectContent>尺寸
SelectTrigger 的 size 提供 sm / md / lg。
<Select items={fonts} defaultValue="mono">
<SelectTrigger size="sm" />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>无效态
SelectTrigger 传 invalid 标红(独立使用时)。
<Select items={fonts} placeholder="请选择字体">
<SelectTrigger invalid />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>禁用态
Select 传 disabled 屏蔽整个下拉。
<Select items={fonts} defaultValue="sans" disabled>
<SelectTrigger />
<SelectContent>{/* SelectItem… */}</SelectContent>
</Select>何时用
从一组固定选项里选一项或多项(选项较多、需要收纳成下拉)。多选传 multiple,受控值变 string[],Trigger 平铺已选 label、超出折叠 +N。选项少且需全部可见用 Radio 或 CheckboxGroup(多选平铺);自由文本用 Input。给 items({value,label} 数组)让 Trigger 显示选中项 label 而非 raw value。
对标 el-select 的 clearable / filterable 心智:本组件的 clearable 即前者,searchable 即后者(内部切到 Combobox 的搜索皮肤,过滤逻辑直接复用 Base UI Combobox,不另造)。需要 chips 多选输入、异步远程补全、自由输入等更重的场景仍直接用 Combobox。
导入
import { Select, SelectTrigger, SelectContent, SelectItem, SelectGroup, SelectGroupLabel } from "@hulianui/ui"Props
Select 继承 Base UI Select.Root 属性(除 items 被下方覆盖外,如 value/defaultValue/onValueChange/disabled…)。
Select
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| items | ReadonlyArray<{ value: string | null; label: ReactNode }> | — | 选项数据;Base UI 据此让 Trigger 显示选中项 label |
| placeholder | ReactNode | — | 无选中值时的占位文本(单选注入 value:null 项实现;多选由 Trigger 函数式 Value 渲染) |
| multiple | boolean | false | 多选模式:value/defaultValue/onValueChange 均为 string[];选中后浮层保持打开 |
| clearable | boolean | false | 有值时 Trigger 右侧 hover/focus 浮出清除按钮,点击置空(单选回传 null,多选回传 []) |
| searchable | boolean | false | 切到 Combobox 搜索皮肤:浮层顶部搜索框 + Base UI 过滤(依赖 items) |
| searchPlaceholder | string | "搜索" | searchable 时搜索框占位 |
| emptyMessage | ReactNode | "无匹配项" | searchable 时无命中的空态文案 |
| virtualized | boolean | items ≥ 100 时为 true | searchable 皮肤下的列表虚拟化;标准皮肤不涉及。见「禁忌 / 坑」 |
| loading | boolean | false | 加载态:Trigger 图标换 Spinner,浮层只出加载占位(不渲染选项) |
| loadingText | ReactNode | "加载中" | 加载占位文案 |
SelectGroup / SelectGroupLabel
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| children* | ReactNode | — | SelectGroup 内放一个 SelectGroupLabel + 若干 SelectItem |
| className | string | — | 透传类名 |
SelectTrigger
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 尺寸 |
| invalid | boolean | false | 独立使用(非 Field 内)时手动置无效态皮肤 |
| maxDisplay | number | 2 | 多选模式下最多平铺几个已选 label,超出折叠为 +N 计数 |
| className | string | — | 透传类名 |
SelectContent
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| side | "top" | "bottom" | "bottom" | 弹出方向 |
| align | "start" | "center" | "end" | — | 对齐 |
| sideOffset | number | — | 偏移量 |
SelectItem
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| value* | string | — | 选项值(本批仅 string 值) |
| disabled | boolean | false | 禁用此项 |
Events
Select 透传 Base UI Select.Root 的常用事件。
| 事件 | 类型 | 说明 |
|---|---|---|
| onValueChange | (value: string | null, eventDetails) => void(多选时 (value: string[], …)) | 选中值变化回调(透传 Base UI Select.Root) |
| onOpenChange | (open: boolean, eventDetails) => void | 下拉开合变化回调(透传 Base UI Select.Root) |
Slots
| 插槽 | 类型 | 说明 |
|---|---|---|
| SelectContent.children* | ReactNode | 一组 SelectItem(可外套 SelectGroup) |
| SelectItem.children* | ReactNode | 选项展示内容 |
| SelectGroupLabel.children* | ReactNode | 分组标题 |
禁忌 / 坑
- 占位文本通过
Select的placeholderprop 传,不要给Select.Value传 placeholder——见 [[base-ui-select-rc0-no-value-placeholder-prop-inject-null-item]]:本项目锁 Base UI rc.0,其Select.Value没有 placeholder prop(那是 v1.2+),瑚琏靠注入一个value:null的 items 项实现占位 label。items与SelectItem的 value 要对应,否则 Trigger 显示 raw value 而非 label。 multiple下 value 必须是数组:给defaultValue="a"(字符串)会被当成无选中处理。多选模式不注入 null 占位项(数组值命不中 null 项),占位由 Trigger 内函数式 Value 渲染,因此多选的 placeholder/label 解析依赖 `items` prop——不传 items 时 Trigger 只能显示 raw value。- 多选 Trigger 的平铺条数由
SelectTrigger的maxDisplay控制(默认 2),不在Select上。 clearable会让组件接管 value(非受控时内部起受控镜像)——受控用法不变:外部不改value时点清除只回调不落值,符合受控语义。未开clearable时 value 归属与 DOM 结构与旧版逐字节一致(不加包裹层)。- 清除按钮是
Trigger的兄弟节点(绝对定位盖在箭头位上),不是子节点——<button>里嵌<button>是非法 HTML,且嵌套后点击会冒泡到 Trigger 顺手把浮层打开。常态hidden,靠外层group-hover/group-focus-within浮出。 searchable依赖items:该皮肤下列表由items过滤结果驱动渲染(消费者写的SelectItem按 value 建索引后复用,自定义内容不丢;items有而SelectItem没写的项兜底用 label 渲染)。不传 `items` 就没有候选,浮层恒为空态。searchable下选项会被拍平,SelectGroup不生效(Base UI Combobox 的分组要求items本身是分组结构,与 Select 的声明式分组不是一套)。需要"搜索 + 分组"直接用 Combobox。searchable的过滤匹配 label 的字符串形态;label 传 ReactNode(如带图标的 JSX)时退回按value匹配。要按中文/拼音/编码多字段搜,走 Combobox 自带filter。searchable下items给到 100 项及以上时列表自动虚拟化(底层 Combobox 的策略):只有视口内的选项在 DOM 里,行高按 32px 固定估算、不逐项测量。默认SelectItem恰好 32px,通常无感。如果你的SelectItem高度不是 32px(两行文案、带头像、自定义 padding/字号),那么在 ≥100 项时滚动落位会逐渐偏移——不报错、短列表也复现不出来,请显式传virtualized={false}。同理,测试里getAllByRole("option")在虚拟化后只拿得到视口内那几条。loading期间浮层只出占位、不渲染任何选项(避免展示上一轮的陈旧数据),且不给清除按钮(值可能正在刷新)。
相关
Input · Textarea · Checkbox · CheckboxGroup · Radio · Switch
Playground
<Select items={items} placeholder="请选择字体" defaultValue="…">
<SelectTrigger size="md" />
<SelectContent side="bottom">
{items.map((it) => <SelectItem key={it.value} value={it.value}>{it.label}</SelectItem>)}
</SelectContent>
</Select>