定价

Select 使用手册

v3.3.0 · @snowui-design-system/components

许可阅读:首次阅读组件文档、源码或 npm 使用流程,先读完整 LICENSE;同任务同版本去重。公开 API 配置与内部实现修改分别按许可处理;全部组件使用统一运行包,源码权益以许可为准。

组件介绍

在下拉列表中选择选项,可由数据直接生成面板,也可自定义选项组合,支持搜索与键盘操作。

使用选择

用于下拉单选。默认传 options 生成 Listbox / MenuItem;显式 children 替换默认选项面板。命令菜单优先 SelectButton;不是任意多选或自由输入控件。

  • options 默认生成列表;显式 children 提供 Listbox / MenuItem 后替换默认列表。

  • persistent 保存选择;action 只调用 onAction 并关闭,不保存本次选择。默认子项用 parts,显式子项直接配置。

  • 列表通过 Escape 关闭后,字段型与 buttonTrigger/SelectButton 模式均恢复真实触发按钮焦点;搜索头关闭行为由共享 Listbox 管理。

快速开始

以下示例展示推荐的基本组合。更多场景与组合限制见后文。

<script lang="ts">	import { Select, Listbox, MenuItem, Text, Button } from '@snowui-design-system/components';</script> <Select placeholder="请选择城市" style="width: 20rem; max-width: 100%;">    <Listbox>        <MenuItem as="item" value="shanghai">Shanghai</MenuItem>        <MenuItem as="item" value="hangzhou">Hangzhou</MenuItem>        <MenuItem as="item" value="shenzhen">Shenzhen</MenuItem>    </Listbox></Select>

API

关键字 类型 默认值 描述
gap number — 继承 Frame 的间距 token 索引;省略时保留组件原首选间距,显式值覆盖首选间距,有效图文间距仍至少为 4 CSS px;显式零和根字号/间距缩放不压低下限。
value SelectValue — 当前值,支持 bind:value
defaultValue SelectValue — 初始值
placeholder string — 空值占位文字,显示在 Trigger 中
name string — 表单提交字段名
disabled boolean false 禁用状态
readonly boolean false 只读状态
required boolean false 必填状态
form string — 指定外部表单归属
autofocus boolean — 首次渲染自动聚焦
tabindex number — Tab 顺序
title string — 原生提示
id string — 原生 id
invalid boolean false 组件级无效态
children Snippet — <Listbox> + <MenuItem> 组合内容
options SelectOption[] [] 默认内置列表数据,字段遵循 ListboxItem;传入显式 children 时不渲染该默认列表。
triggerContent Snippet<[SelectTriggerContext]> — buttonTrigger 开启时替换触发按钮内容;接收 value、option、label、placeholder、open、disabled。
buttonTrigger boolean false 使用 Button 代替字段型触发器;保留展开、选择与焦点恢复协议。
buttonBare boolean true 只在 buttonTrigger 模式生效,控制内置 Button 是否为无外壳 bare 外观。
selectionMode "persistent" | "action" "persistent" persistent 保存选择值;action 只执行 onAction、关闭面板并恢复触发器焦点,不保存本次选项。
onAction (value: string) => void — action 模式选择选项后调用,参数为字符串选项值;此时面板已关闭,返回值不参与选择。
parts SelectParts — 配置默认 Listbox、Input、MenuItem 与按钮触发器;显式 child 不消费父级 parts。
ariaLabel string — 触发器的可访问名称;按钮触发模式无自定义内容时也作为后备显示内容。
onclick (event: MouseEvent) => void — 非 disabled、非 readonly 的触发器点击切换展开状态后调用,接收原始 MouseEvent。
oninput (event: Event) => void — persistent 模式选择了不同值时调用,接收隐藏表单输入派发的 change Event;实际新值从绑定 value 读取,不是原生文本输入过程。
onchange (event: Event) => void — persistent 模式选择值改变后调用,接收同一次 change Event;相同值与 action 模式不触发。
onfocus (event: FocusEvent) => void — 字段型触发器取得焦点时调用,接收 FocusEvent;按钮触发模式不使用此直接处理器。
onblur (event: FocusEvent) => void — 字段型触发器失去焦点时调用,接收 FocusEvent;按钮触发模式不使用此直接处理器。
onkeydown (event: KeyboardEvent) => void — 触发器的内置方向键、Enter/Space 和 Escape 处理后调用,接收 KeyboardEvent;不能撤销已处理的动作。
onkeyup (event: KeyboardEvent) => void — 字段型触发器松开按键时调用,接收 KeyboardEvent;按钮触发模式不使用此直接处理器。

未重新声明的继承能力请查所属基础组件文档;本表只列本组件源码具名公开属性。

使用边界与详细说明

默认内置模式 / 显式组合模式

  • 默认内置模式(推荐):传入 options,Select 自动生成 Listbox 与 MenuItem;搜索框默认关闭,通过 parts.Listbox.search=true 开启;通过 parts.Button、parts.Listbox、parts.Input、parts.MenuItem 配置隐藏实例。

  • options 中同一 value 的 label 或 disabled 更新时,当前选择的显示标签、搜索与键盘选项按新数据更新;调用方负责提供目标语言的业务文案。

  • 显式组合模式:显式书写 Listbox / Input / MenuItem,用于复杂内容或逐项差异;子组件 Props 直接写在实例上。

  • 显式 children 存在时替换默认内置 Listbox,不同时激活两套选项。

Select 内的 Listbox 默认 search=false。显式组合时使用 <Listbox search> 开启;默认内置模式使用 parts={{ Listbox: { search: true } }} 开启。独立 Listbox 的默认值不受影响。

<Select    options={[{ value: "a", label: "A" }]}/>

buttonTrigger 可将字段型 trigger 切换为真正的 bare Button;优先使用 triggerContent snippet,未提供时依次回退到选中项、placeholder、ariaLabel。适合 TableToolbar 的排序、筛选等图标操作。按钮保持 aria-haspopup="listbox"、aria-expanded、键盘导航和选中后焦点恢复。该模式的 Select 外层是无视觉 wrapper,不接入独立字段的 .snowui-field-bordered 外壳,最终背景、边框、尺寸与圆角只由真实 Button 宿主拥有。

2026-04-20 更新: 新增搜索 + 自定义值支持 — filterText / customOptions 状态由 Select 持有,内嵌 <Input as="search"> 可连接过滤逻辑;内嵌 Input 的 allowCustomValue prop 允许用户添加自定义选项;该属性不属于 Select。Combobox 已废弃。

2026-04-18 架构升级: Select v3.0.0 — Listbox 拆分为独立组件,API 从 <option> 切换到 <Listbox> + <MenuItem> 组合模式,表单提交改用 hidden <input>,不再依赖隐藏 <select> + MutationObserver。

2026-04-04 补充: 参考 Figma 9996:17631,Select 单独使用时默认保持 12 / 16 padding 与 8px gap;进入 Field 后,只看有没有 as="segment"。未标 segment 时由外层 Field 处理 12 / 16 与 gap = 8,Select 去掉自身 padding,但保留自身 gap;标了 segment 时保留 Select 自身的 padding 与 gap,并作为连续字段表面的一段参与分割线。

继承: 复用 Frame.svelte 的所有通用能力(状态、动效、响应式、Skeleton、样式编排)。 推荐容器: Field.svelte。 默认能力开关: enableResponsive=false, enableContext=true, enableScaleObserver=false。 自动布局协议: Select 根元素为 <Frame row>,trigger(content/fill)+ indicator(trailing/hug)由自动布局协议管理。

字段型原生 trigger 显式接入 layout.css 的 .snowui-native-control,只归一化 box sizing、margin 并继承消费环境的字体与颜色;Select 自身字号、行高、Padding、边框和状态视觉不依赖可选的 snowui-base.css。buttonTrigger 模式由 Button / Frame 的同一宿主基线承接。

2026-04-27 更新: Select v3.1.0 — 支持直系复杂 leading / trailing 槽位;自定义 trailing 替换默认 indicator。

显式内容与默认槽位

  • 直系 Frame as="leading" / Frame as="trailing" 支持复杂结构;图形与短标签并排时使用 singleLine 和 Text lines={1},内部单行与最小间距由附件 Frame 管理。

  • Select 根布局为 leading: auto、trigger: minmax(0, 1fr)、trailing: auto。

  • 存在自定义直系 as="trailing" 时,默认 indicator 隐藏,由自定义 trailing 替换末位槽。

槽位组合边界

复杂附件需要为可编辑/可选内容保留宽度;下例显式设置最小240 CSS px,并用ScrollArea在更窄容器中局部横向滚动。

  • 支持直系复杂 leading / trailing 槽位。

  • 自定义 trailing 替换默认 indicator。

表单组合与间距

  • 基础节奏统一:Field 默认 padding = 12 / 16,gap = 8

  • Select 单独使用时:组件自身带 12 / 16 padding 和 8 gap

  • 放进 Field 后,先看有没有 as="segment"

  • 没有 as="segment":由外层 Field 负责 12 / 16 padding 与 8 gap;Select 去掉自身 padding,但保留自身 gap

  • 有 as="segment":这是连续字段表面的一段;保留 Select 自身的 padding 与 gap

  • segment 之间才有分割线:横向是左右分割线,纵向是上下分割线

  • 相邻 segment 之间只能有一条分割线,不能重复画线

  • label / description / error 不负责 padding、gap、分割线,它们只是内容元素

  • 普通 label + host + error:不作为 padding、gap 的判断依据,只负责文字内容

  • Field row 或 Field column 里,如果是连续输入段,子项要显式写 as="segment"

组件定位

Select 是自定义下拉选择框,采用 Trigger + Listbox 组合架构:

  • 自身渲染 Trigger 按钮 + ArrowLineUpDown 指示器

  • 子内容为 <Listbox> + <MenuItem> 组合,Listbox 自行 portal 到 body

  • 通过 ListboxOwnerContext(setContext)与子组件通信

  • 表单提交通过 hidden <input type="hidden"> 桥接

  • 标签、错误提示与连续字段段逻辑建议通过 Field 容器组合完成

  • 单独使用时可保持兼容性的默认输入框表现

Frame 能力继承

Select 仍然通过外层 Frame 继承:

  • responsive / stateStyle / customStates / variants / styles

  • Hover / Click / Focus / whileAppear / timeline / orchestrate

  • loading / skeleton / renderHints

  • as / use / layoutId / itemId / scope / access

因此 Select 不是一个单纯的裸 <select>,而是 Frame + 原生 select host。

错误反馈 Shake

Select invalid / aria-invalid 默认自动播放错误 shake。单独使用时由 Select 自身反馈;进入 Field 后,嵌入态 Select 不单独抖动,由外层 Field 汇总后整体反馈。

错误态与 shake 不会自动聚焦,也不会模拟 focus-within;焦点态只来自用户真实聚焦、显式 autofocus 或业务代码主动调用 focus()。

布局模型

主布局模型:槽位宿主布局。定义见 ../../../specs/maintainers/LayoutProtocol.md §12.3(源码参考:specs/maintainers/LayoutProtocol.md)。

普通字段模式的根宿主固定采用单行内容协议,选中值可收缩,图标与标签的有效间距至少为 4 CSS px;gap={0}、根字号或间距缩放不压低下限,较大首选间距保持。buttonTrigger 模式由真实 Button 拥有图文内容,Select 的无视觉定位外壳不增加空白间距。自定义 children 保留原结构,弹层与表单协议不变。

Select 的根布局固定按 Leading / Trigger / Trailing 三段组织:

  • 根节点是 Frame row,并在组件内部固定启用 enableAutoLayout;外部传入 enableAutoLayout={false} 不关闭 Select 的内部槽位协议。

  • trigger 是 Content / Fill 槽,负责显示选中值或 placeholder,必须保持 min-width: 0 与文本截断能力。

  • 默认 indicator 是 Trailing / Hug 槽。

  • 存在直系自定义 as="trailing" 时,默认 indicator 隐藏,由自定义 trailing 替换末位槽。

  • Listbox / MenuItem 是 Select 的弹层内容协议,不参与 Select 根三槽位布局。

能力边界

Select 负责:

  • 单选下拉选择(Trigger + Listbox 组合架构)

  • value / defaultValue 解析

  • ARIA combobox + listbox 完整语义

  • 键盘导航(↑↓ / Enter / Space / Escape / Tab)

  • 通过 hidden <input> 桥接表单提交

  • 通过 ListboxOwnerContext 驱动子 Listbox / MenuItem

  • 持有 filterText 与 filteredOptions 状态,为内嵌 Input as="search" 提供过滤能力

  • 持有 customOptions 列表,支持用户在运行时添加 / 删除自定义选项

Select 不负责:

  • 业务级选项获取与远程联想

  • 多选模式(未来可扩展)

使用示例

 <Field>  <Select placeholder="请选择状态">    <Listbox>      <MenuItem value="todo">待处理</MenuItem>      <MenuItem value="doing">处理中</MenuItem>      <MenuItem value="done">已完成</MenuItem>    </Listbox>  </Select></Field>  <Select placeholder="请选择城市">  <Listbox>    <Input as="search" placeholder="搜索..." />    <MenuItem value="beijing">北京</MenuItem>    <MenuItem value="shanghai">上海</MenuItem>    <MenuItem value="hangzhou">杭州</MenuItem>  </Listbox></Select>  <Select placeholder="请选择或输入">  <Listbox>    <Input as="search" allowCustomValue placeholder="搜索或输入新选项..." />    <MenuItem value="tag-a">Tag A</MenuItem>    <MenuItem value="tag-b">Tag B</MenuItem>  </Listbox></Select>  <Select name="priority" required placeholder="请选择优先级">  <Listbox>    <MenuItem value="low">低</MenuItem>    <MenuItem value="medium">中</MenuItem>    <MenuItem value="high">高</MenuItem>  </Listbox></Select>  <ScrollArea axis="horizontal" ariaLabel="Select with attachments" width="100%"><Select minWidth={240} placeholder="Tags">  <Frame as="leading" singleLine>    <Icon as="leading" icon={Resource.Tag} />    <Text lines={1}>Tags</Text>  </Frame>  <Frame as="trailing" singleLine>    <Text class="text-black-40" lines={1}>Multi-Select</Text>    <Icon as="trailing" icon={Resource.CaretRight} class="text-black-20" />  </Frame>  <Listbox>    <MenuItem as="item" value="design">Design</MenuItem>    <MenuItem as="item" value="code">Code</MenuItem>  </Listbox></Select></ScrollArea>

无障碍与表单建议

  • Trigger 使用 role="combobox" + aria-haspopup="listbox" + aria-expanded + aria-controls

  • Listbox 使用 role="listbox",每个选项 role="option" + aria-selected

  • 键盘导航完整支持:↑↓ 移动焦点,Enter/Space 确认,Escape 关闭,Tab 关闭

  • 如果需要错误提示,建议通过 aria-describedby 关联 Field 内错误节点

  • 如果需要搜索型下拉或异步联想,应使用 Combobox 组件


Select 样式覆盖

先阅读 完整 LICENSE;同任务、同版本、同范围去重。

优先用组件公开 Props 匹配合适样式,尽可能保留原有视觉。确需改写时按 样式选择与改写顺序 使用自己的 class(含项目已有 Tailwind)或实例 style。以下名称来自源码实际消费的公开 override;先核对目标作用域,不修改私有 DOM。采用 SnowUI Token 时选择正确类别,外部应用不被要求安装一方 utilities。

变量 用途与默认参考
--snowui-select-background-override 背景(默认值见组件源码)
--snowui-select-font-size-override font-size(默认值见组件源码)
--snowui-select-gap-override gap(默认 var(--gap-2) = 8px)
--snowui-select-height-override 高度(默认值见组件源码)
--snowui-select-indicator-color-override 指示器颜色(默认值见组件源码)
--snowui-select-indicator-size-override 指示器尺寸(尺寸 px,默认值见组件源码)
--snowui-select-line-height-override line-height(默认值见组件源码)
--snowui-select-padding-override 整体 padding(默认值见组件源码)
--snowui-select-placeholder-color-override 占位符颜色(默认值见组件源码)
--snowui-select-radius-override border-radius(默认 var(--radius-4) = 16px)
--snowui-select-width-override 宽度(默认值见组件源码)

设计指南 · Token · 组合边界

Select 类型与状态

必填属性:无具名必填属性。

可双向绑定:bind:value。

继承声明:Omit<FrameProps, "children" | "tag">。继承能力以基类公开文档为准;Omit 中的字段已排除,不能按基类原样使用。

共享 Frame API · 返回 API

以下为公开属性实际引用的类型声明,来自源码;Svelte Snippet 表示内容片段,ComponentProps 表示对应组件的 Props。

SelectValue

来源:components/src/components/Select.svelte

export type SelectValue = string | number | undefined;

SelectOption

来源:components/src/components/Select.svelte

export type SelectOption = ListboxItem;

ListboxItem

来源:components/src/components/Listbox.svelte

export interface ListboxItem {        value: string;        label: string;        disabled?: boolean;    }

SelectTriggerContext

来源:components/src/components/Select.svelte

export interface SelectTriggerContext {        value: SelectValue;        option: SelectOption | undefined;        label: string | undefined;        placeholder: string | undefined;        open: boolean;        disabled: boolean;    }

SelectParts

来源:components/src/components/Select.svelte

type SelectParts = ComponentPartsOf<{        Listbox: typeof Listbox;        Input: typeof Input;        MenuItem: typeof MenuItem;    }> & {        Button?: Omit<            ComponentProps<typeof Button>,            "children" | "onclick" | "onkeydown" | "disabled"        >;    };

ComponentPartsOf

来源:components/src/logic/component-parts.svelte.ts

export type ComponentPartsOf<    T extends Record<string, Component<any>>,> = {    [K in keyof T]?: ConfigurableComponentPartProps<T[K]>;};

ConfigurableComponentPartProps

来源:components/src/logic/component-parts.svelte.ts

export type ConfigurableComponentPartProps<C extends Component<any>> = Partial<    Omit<ComponentProps<C>, ComponentPartControlledKey>>;

ComponentPartControlledKey

来源:components/src/logic/component-parts.svelte.ts

export type ComponentPartControlledKey =    | "children"    | "as"    | "value"    | "defaultValue"    | "open"    | "selected"    | "checked"    | "disabled"    | "readonly"    | "id"    | `on${string}`    | `aria-${string}`;