Select
- 组件说明:单选下拉选择器,可选开启“触发器即输入框”的搜索过滤。对外只提供声明式
options配置 API,触发器无箭头图标,对齐 Figma 规范。 - 尺寸基线:触发器最小高度 40px、水平内边距 16px、圆角 5px。菜单项内边距 16px × 8px。
- 实现约定:
ui/select通过unstyledVisual保留结构骨架,design 层统一接管所有子组件的边框、颜色、阴影和状态视觉。 - Figma 规范
基础用法
声明式 options API — 最简洁的使用方式。设置 searchable 后,触发器会变成可输入的搜索框,并在输入时过滤下拉选项。
结果
Loading...
实时编辑器
render( <div style={{ width: 280 }}> <Select placeholder="请选择成员角色" options={[ { value: 'owner', label: 'Owner' }, { value: 'admin', label: 'Admin' }, { value: 'editor', label: 'Editor' }, { value: 'viewer', label: 'Viewer' }, { value: 'commenter', label: 'Commenter' }, { value: 'guest', label: 'Guest' }, { value: 'billing', label: 'Billing' }, { value: 'auditor', label: 'Auditor' }, { value: 'developer', label: 'Developer' }, { value: 'support', label: 'Support' }, ]} /> </div>, )
分组选项
使用 type: 'group' 和 type: 'separator' 实现分类菜单。
结果
Loading...
实时编辑器
render( <div style={{ width: 280 }}> <Select placeholder="请选择团队" options={[ { type: 'group', label: 'Workspace', options: [ { value: 'design', label: 'Design' }, { value: 'engineering', label: 'Engineering' }, ], }, { type: 'separator' }, { type: 'group', label: 'Status', options: [ { value: 'active', label: 'Active' }, { value: 'pending', label: 'Pending', disabled: true }, ], }, ]} /> </div>, )
状态
触发器状态对齐 Figma 变体矩阵:State × Filled × Status。
结果
Loading...
实时编辑器
render( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, maxWidth: 760, }} > <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Default</span> <Select placeholder="请选择" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Default</span> <Select defaultValue="banana" placeholder="请选择" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Error</span> <Select placeholder="错误状态" error options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Error</span> <Select defaultValue="banana" error options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Empty</span> <Select disabled placeholder="不可用" options={[{ value: 'apple', label: 'Apple' }]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Filled</span> <Select defaultValue="banana" disabled placeholder="不可用" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> </div>, )
搜索模式
设置 searchable 后,触发器本身会变成输入框,输入时即可实时过滤下拉选项。
结果
Loading...
实时编辑器
render( <div style={{ width: 280 }}> <Select placeholder="搜索并选择" searchable options={[ { value: 'alice', label: 'Alice Johnson' }, { value: 'bob', label: 'Bob Smith' }, { value: 'charlie', label: 'Charlie Brown' }, { value: 'diana', label: 'Diana Prince' }, { value: 'eve', label: 'Eve Williams' }, ]} /> </div>, )
触发器 Token 表
| 状态 | 边框 | 背景 | 说明 |
|---|---|---|---|
| Default | Separators/Emphasized#CCCCCC | 透明 | 未展开 |
| Focused / Open | Labels/Primary#000000 | 透明 | 展开或聚焦 |
| Error | Status/Destructive#FF503F | 透明 | error 属性 |
| Disabled | Separators/Emphasized#CCCCCC | Grays/Gray-1#EBEBEB | disabled 属性 |
菜单 Token 表
| 元素 | 属性 | Token / 值 |
|---|---|---|
| Content | 背景 | Grays/White#FFFFFF |
| Content | 边框 | Separators/Default#EBEBEB |
| Content | 阴影 | Effects/Shadow/Defaultrgba(0,0,0,0.1) |
| Content | 圆角 | Radius_5 (5px) |
| Content | 最小宽度 | 160px |
| Content | 垂直内边距 | Spacing_8 (8px) |
| Item | 文字颜色 | Labels/Secondary#3D3D3D |
| Item | 悬停背景 | Grays/Gray-1#EBEBEB |
| Item(禁用) | 文字颜色 | Labels/Disabled#A3A3A3 |
| Label | 文字颜色 | Labels/Tertiary#757575 |
| Separator | 颜色 | Separators/Non-opaquergba(0,0,0,0.08) |
尺寸规范
| 维度 | 值 |
|---|---|
| 触发器最小高度 | 40px |
| 触发器水平内边距 | 16px |
| 触发器圆角 | 5px |
| 触发器字体 | Body/Regular(14px,行高 22) |
| 触发器箭头图标 | 无(Figma 规范隐藏) |
| 内容最小宽度 | 160px |
| 内容圆角 | 5px |
| 内容垂直内边距 | 8px |
| 菜单项水平内边距 | 16px |
| 菜单项垂直内边距 | 8px |
| 菜单项字体 | Body/Regular(14px,行高 22) |
| 分割线水平外边距 | 16px |
| 分割线垂直外边距 | 4px |
Props
Select(声明式)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
options | SelectOptionEntry[] | - | 声明式选项配置 |
placeholder | string | - | 占位文案 |
value | string | - | 受控值 |
defaultValue | string | - | 初始值(非受控) |
searchable | boolean | false | 启用下拉面板内搜索 |
error | boolean | false | 触发器错误状态 |
disabled | boolean | false | 禁用 |
open | boolean | - | 受控展开状态 |
defaultOpen | boolean | false | 初始展开状态 |
onValueChange | (value: string) => void | - | 选中值变化回调 |
onOpenChange | (open: boolean) => void | - | 展开状态变化回调 |
triggerClassName | string | - | 触发器自定义类名 |
contentClassName | string | - | 内容层自定义类名 |
aria-label | string | 'select' | 无障碍标签 |
SelectOptionEntry
// 基础选项
{ value: string; label: ReactNode; disabled?: boolean }
// 分组
{ type: 'group'; label: ReactNode; options: SelectOptionItem[] }
// 分割线
{ type: 'separator' }
MultiSelect(多选 Chips 模式)
多选场景使用 MultiSelect,已选值在触发器中显示为可移除的 Chip 标签。内置输入框支持搜索过滤,可选开启输入创建新选项。
结果
Loading...
实时编辑器
render( <div style={{ width: 320 }}> <MultiSelect placeholder="添加邮箱" options={[ { value: 'alice@example.com', label: 'alice@example.com' }, { value: 'bob@example.com', label: 'bob@example.com' }, { value: 'charlie@example.com', label: 'charlie@example.com' }, { value: 'a_very_long_email_address@gmail.com', label: 'a_very_long_email_address@gmail.com', }, ]} defaultValue={['alice@example.com', 'bob@example.com']} /> </div>, )
MultiSelect 状态
结果
Loading...
实时编辑器
render( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, maxWidth: 760, }} > <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty</span> <MultiSelect placeholder="添加邮箱" options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled</span> <MultiSelect placeholder="添加邮箱" options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} defaultValue={['a', 'b']} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Error</span> <MultiSelect placeholder="添加邮箱" error options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} defaultValue={['a']} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled</span> <MultiSelect placeholder="添加邮箱" disabled options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, { value: 'c', label: 'my_name_is_too_long_to_read@gmail.com' }, ]} defaultValue={['a', 'b', 'c']} /> </div> </div>, )
搜索过滤
在输入框中输入即可过滤选项。输入为空时按 Backspace 可删除最后一个 Chip。
结果
Loading...
实时编辑器
render( <div style={{ width: 320 }}> <MultiSelect placeholder="搜索并选择成员" searchable options={[ { value: 'alice', label: 'Alice Johnson' }, { value: 'bob', label: 'Bob Smith' }, { value: 'charlie', label: 'Charlie Brown' }, { value: 'diana', label: 'Diana Prince' }, { value: 'eve', label: 'Eve Williams' }, ]} /> </div>, )
可创建新选项
设置 searchable + creatable 后可通过输入 + 回车创建新选项。
结果
Loading...
实时编辑器
render( <div style={{ width: 320 }}> <MultiSelect placeholder="添加标签(输入 + 回车)" searchable creatable options={[ { value: 'bug', label: 'Bug' }, { value: 'feature', label: 'Feature' }, { value: 'docs', label: 'Documentation' }, ]} defaultValue={['bug']} /> </div>, )
MultiSelect Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
options | MultiSelectOption[] | - | 选项列表(必填) |
placeholder | string | - | 占位文案 |
value | string[] | - | 受控选中值 |
defaultValue | string[] | [] | 初始值(非受控) |
onChange | (value: string[]) => void | - | 选中变化回调 |
searchable | boolean | false | 启用内联搜索输入 |
creatable | boolean | false | 允许输入回车创建新选项(需同时开启 searchable) |
onCreate | (input: string) => MultiSelectOption | - | 自定义创建回调 |
error | boolean | false | 错误状态 |
disabled | boolean | false | 禁用 |
triggerClassName | string | - | 触发器自定义类名 |
contentClassName | string | - | 内容层自定义类名 |
aria-label | string | 'multi-select' | 无障碍标签 |
MultiSelectOption
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | - | 选项值(必填) |
label | ReactNode | - | 显示文案 |
searchText | string | - | 自定义搜索文本(label 为 ReactNode 时使用) |
disabled | boolean | false | 禁用 |