跳到主要内容

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 表

状态边框背景说明
DefaultSeparators/Emphasized#CCCCCC透明未展开
Focused / OpenLabels/Primary#000000透明展开或聚焦
ErrorStatus/Destructive#FF503F透明error 属性
DisabledSeparators/Emphasized#CCCCCCGrays/Gray-1#EBEBEBdisabled 属性

菜单 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(声明式)

属性类型默认值说明
optionsSelectOptionEntry[]-声明式选项配置
placeholderstring-占位文案
valuestring-受控值
defaultValuestring-初始值(非受控)
searchablebooleanfalse启用下拉面板内搜索
errorbooleanfalse触发器错误状态
disabledbooleanfalse禁用
openboolean-受控展开状态
defaultOpenbooleanfalse初始展开状态
onValueChange(value: string) => void-选中值变化回调
onOpenChange(open: boolean) => void-展开状态变化回调
triggerClassNamestring-触发器自定义类名
contentClassNamestring-内容层自定义类名
aria-labelstring'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

属性类型默认值说明
optionsMultiSelectOption[]-选项列表(必填)
placeholderstring-占位文案
valuestring[]-受控选中值
defaultValuestring[][]初始值(非受控)
onChange(value: string[]) => void-选中变化回调
searchablebooleanfalse启用内联搜索输入
creatablebooleanfalse允许输入回车创建新选项(需同时开启 searchable
onCreate(input: string) => MultiSelectOption-自定义创建回调
errorbooleanfalse错误状态
disabledbooleanfalse禁用
triggerClassNamestring-触发器自定义类名
contentClassNamestring-内容层自定义类名
aria-labelstring'multi-select'无障碍标签

MultiSelectOption

属性类型默认值说明
valuestring-选项值(必填)
labelReactNode-显示文案
searchTextstring-自定义搜索文本(label 为 ReactNode 时使用)
disabledbooleanfalse禁用