定价

Listbox 使用手册

v1.4.0 · @snowui-design-system/components

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

组件介绍

在浮层中展示可选择的选项,支持搜索过滤与键盘操作,可作为 Select 的选择面板。

使用选择

用于有选项、键盘导航和选择语义的列表面板,通常由 Select 承载。通知、联系人、活动流是内容列表,不使用 Listbox。

  • items 的 value 必须唯一,label 用于显示和搜索;children 存在时替换 items。

  • 显式选项使用 MenuItem as="item";显式搜索使用 Input as="search" 并关闭内置 search,避免重复输入框。

  • 默认内置搜索占位通过 listbox.search 随 LocaleProvider 切换;parts.Input.placeholder(含空字符串)与原生 aria-label 保持优先,显式 Input 子项由调用方配置,查询和选项内容不翻译。

快速开始

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

<script lang="ts">	import { Listbox, Select, Text } from '@snowui-design-system/components';	const pages = ['Accordion', 'Avatar', 'Badge', 'Breadcrumb', 'Button', 'Card', 'Checkbox', 'Chip', 'Dialog', 'Field', 'Input', 'Listbox', 'MenuItem', 'Popover', 'Select', 'Tooltip'].map(label => ({ value: label, label }));	let page = $state('Button');</script> <div style="display: flex; flex-direction: column; gap: var(--gap-4); width: min(100%, 22rem); min-width: 0;"><Select bind:value={page} ariaLabel="Component documentation" style="width: 100%;">    <Listbox search items={pages} parts={{ Input: { placeholder: 'Find a component…', 'aria-label': 'Find a component' } }} /></Select></div>

API

关键字 类型 默认值 描述
children Snippet — 无参数的显式选项内容;传入后不生成 items。选项使用 MenuItem as="item" 并提供唯一 value;外置搜索使用 Input as="search",同时设置 search={false} 避免重复内置搜索框。
items ListboxItem[] [] 默认内置选项数组;每项包含必填且唯一的字符串 value、显示及搜索使用的 label、可选 disabled。有 children 时忽略;运行时自定义项由宿主另行管理。
parts ComponentPartsOf<{ Input: typeof Input; MenuItem: typeof MenuItem; Text: typeof Text; }> — 配置默认内置 Input、MenuItem 与字符串标签 Text 的公开 Props,例如搜索 placeholder、allowCustomValue、选项 showCheck 或标签排版;parts.Text 只作用于实际生成的 items/自定义选项标签,显式子项自行配置。parts.MenuItem.showCheck 优先于 Listbox 的 showCheck。
search boolean — 是否生成固定在面板顶部的 Input;依次读取显式值、宿主配置并回退 true。Select 默认向 Listbox 提供 false;需要搜索时显式开启。
maxHeight number | string — 面板最大高度,数字为 px,字符串为 CSS 长度;受可用视口约束。未设置时最多完整显示 10 项,空间不足时至少保留实际选项中的前 2 项。
minWidth number | string — 面板最小宽度,数字为 px,字符串为 CSS 长度;未设置时搜索开启取 160px,否则取 0。内容与视口约束仍参与最终宽度计算。
scrollbar boolean true 溢出时显示浮层滚动条;false 仅隐藏轨道,仍可通过滚轮和键盘滚动。
matchTriggerWidth boolean true true 时以锚点实测宽度加固定 2px 为宽度基准,左右各覆盖 1px,内容需要更宽时允许扩展;false 时按选项内容 Hug,不添加补偿,仍受 minWidth 与视口约束。
placementMode "selected" | "bottom" "selected" selected 将选中项中心对齐触发器;bottom 从触发器下方展开。最终定位与高度受可用视口约束。
align "start" | "end" "start" 面板相对锚点左边缘或右边缘对齐;Hug 面板与锚点宽度不同时差异明显,最终位置受视口约束。
showCheck boolean false 为选中项预留并显示 trailing 勾选;Select 默认提供 true。显式 MenuItem 可用自身 showCheck 覆盖,自定义 trailing 替换默认勾选。
continuousHover boolean true 在直接 MenuItem 之间移动同一个高亮背景,键盘 active 共用该背景;false 恢复逐项即时背景,禁用和过滤项不作为目标。

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

使用边界与详细说明

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

  • 默认内置模式(推荐):items 自动生成 <MenuItem as="item">;设置 search 可生成固定搜索框。未设置 maxHeight 时,面板高度按实际可见 MenuItem 的高度计算:默认最多完整显示 10 项,超过后显示浮层滚动条;通过 parts.Input / parts.MenuItem 配置内置组件。

  • 显式组合模式:显式书写 MenuItem,用于复杂内容或逐项 Props 差异;存在 children 时不再生成 items。

Select 默认提供 search=false;默认内置模式可用 search 开启、search={false} 隐藏搜索框、maxHeight 显式修改最大高度、scrollbar={false} 隐藏浮层滚动条。搜索框缺省 placeholder 通过 listbox.search 随 locale 使用“搜索”(zh-CN)/“Search”(en-US),parts.Input.placeholder(含空字符串)保持优先,有搜索框时面板默认最小宽度为 160px。显式组合模式使用 search 时同样自动插入 Input;不设置时也可设置 search={false} 并手动书写 <Input as="search">。maxHeight 与 scrollbar 始终作为 Listbox 面板能力生效。

未设置 maxHeight 时,可用视口空间只改变完整可见的 Item 数量,不产生半项高度:面板在 2~10 项之间选择能够容纳的完整项数;即使触发器下方空间不足,也至少为现有选项中的前 2 项保留完整高度。少于 2 项时按实际项数 Hug。显式 maxHeight 继续按调用方给定的长度与视口可用高度取较小值。

2026-04-24 更新(v1.2.0): 滚动条间距规范化 — .snowui-listbox 使用 overflow-y: scroll + scrollbar-width: none 隐藏原生滚动条,并通过 .snowui-listbox-body 内的自定义浮层滚动条显示滚动位置。MenuItem 背景左右间距保持 8px;滚动条覆盖在右侧,不占布局宽度,右边缘距面板右边框 4px。带搜索 Input 时,滚动条只出现在 Input 下方的 MenuItem 滚动区域。Thumb 支持鼠标拖拽;打开定位用选中项相对 portal 的真实视觉中心计算,兼容有无顶部 Input 的结构。

2026-04-20 更新: 搜索/自定义值支持 — <Input as="search"> 放入 <Listbox> 可自动接入过滤逻辑;allowCustomValue 允许添加自定义项并支持 hover 删除。移除 Combobox 依赖,Combobox.svelte 已废弃。

自动布局协议: Listbox 外层 portal anchor 只负责 position:fixed 定位与尺寸注入;真正的浮层面板由内层 Frame column 承担,使用 .snowui-quick-action-panel 共享视觉基类,并显式引用 DROPDOWN_OVERLAY_APPEAR_MOTION / DROPDOWN_OVERLAY_DISAPPEAR_MOTION。该 preset 只包含 opacity,不包含 scale;具体 duration / easing 以 motion preset catalog 为准,关闭时保留面板到 disappear preset 完成后再卸载。portal 通过 LISTBOX_HEADER_PORTAL_KEY context 暴露 headerEl 引用;Input as="search" 挂载后自动移入 headerEl(.snowui-listbox-header.snowui-quick-action-panel-header),不参与滚动。Listbox 与 QuickAction 共用同一套 header + scroll-body 结构与间距规则;唯一视觉差异只保留在外层面板 padding。为避免 Frame column 的 auto-layout 默认 gap: 8px 干扰浮层布局,Listbox 根层和 QuickAction 一样显式覆盖为 gap: 0。MenuItem 留在 .snowui-listbox-body.snowui-quick-action-panel-scroll-body > .snowui-listbox(flex:1; min-height:0; overflow-y:scroll; scrollbar-width:none)独立滚动。自定义浮层滚动条挂载在 .snowui-listbox-body 内,定位只覆盖实际滚动的 MenuItem 区域,不进入 Input 区域;滚动条覆盖在右侧,不占布局宽度。间距分工明确:面板 padding 定位 Input 与列表外边距,共享 header 规则负责搜索框与列表的内部衔接,MenuItem 自身 padding 定位选项背景和内容间距。无重复,无冲突。浮层滚动条默认可见宽 4px、距面板右边框 4px、thumb hover 时可见宽 8px 并从右向左增粗。


布局模型

主布局模型:复合管理布局。

  • Listbox 的区域结构为 portal anchor / panel / header / scroll body / option list / overlay scrollbar。

  • Header 与滚动体复用 QuickAction 的面板区域规范;选项行优先复用 MenuItem 的槽位宿主布局。

  • Input as="search" 只作为固定 header 内容移入,不参与滚动列表布局。

  • 默认 items 与运行时自定义选项的字符串标签由 Text lines={1} 显示;较长内容在视口约束下收缩并提供全文访问,勾选/删除图标保持在可见面板内。面板内部继续受外层宽度约束,短标签的 Hug 宽度不改为强制铺满视口。

  • Text 仅拥有默认字符串标签的排版与全文展示;选择值、搜索、注册、键盘和焦点仍由原宿主协议拥有。调用方显式 MenuItem/Snippet 不被自动包裹,也不读取父级 parts.Text。

  • Listbox 不注册为三槽位宿主,也不把 Portal 定位、滚动条和搜索区抽象成通用布局;这些都属于复合组件私有管理逻辑。

与宿主的关系

宿主 Context 提供者 通信方式
Select Select 通过 setContext(LISTBOX_OWNER_CONTEXT_KEY, ...) ListboxOwnerContext

组件 Props

继承 FrameProps,排除 children / tag。Listbox 必须置于 Select 等提供选择上下文的宿主内;展开、当前值和选择事件由宿主拥有,不支持独立 open、value、options、multiple 或 placement 属性。

等宽模式使用锚点实测宽度加固定 2px,左右各外扩 1px,覆盖下层字段边框;保留小数定位精度。该固定 CSS px 补偿不随根字号缩放,适用于所有 Listbox 宿主;Hug 模式不增加补偿,更大的 minWidth / 内容扩展继续生效。规范例外见 StyleSpec §2.1(源码参考:specs/maintainers/StyleSpec.md) 的 USER-LISTBOX-WIDTH-001。

键盘交互

按键 行为
↑ / ↓ 上下移动高亮选项(含自定义项)
Enter / Space(选项列表中) 确认选择高亮项并关闭
Enter(搜索框无匹配 + allowCustomValue) 添加当前输入为自定义项并选中
Escape 搜索头或列表中关闭浮层,焦点还原至 Trigger;无匹配选项时也可关闭;调用方 preventDefault 可取消
Tab 搜索头或列表中关闭浮层,保留原生焦点导航;调用方 preventDefault 可取消
空格、Home / End(搜索框中) 保留原生文本编辑,不选择选项

ARIA

元素 属性
浮层容器 role="listbox", aria-labelledby={triggerId}
每个选项 role="option", aria-selected
高亮项 data-highlighted="true"

默认内置模式(推荐)

<Select placeholder="请选择框架">  <Listbox    search    items={[      { value: "svelte", label: "Svelte" },      { value: "react", label: "React" },      { value: "vue", label: "Vue" }    ]}    parts={{ Input: { placeholder: "搜索框架..." } }}  /></Select>

基础用法(在 Select 中)

<script lang="ts">  import { Select, Listbox, MenuItem } from '@snowui-design-system/components';  let country = $state('us');</script> <Select bind:value={country} name="country">  <Listbox>    <MenuItem as="item" value="us">United States</MenuItem>    <MenuItem as="item" value="cn">China</MenuItem>    <MenuItem as="item" value="jp">Japan</MenuItem>  </Listbox></Select>

带搜索过滤

<Select placeholder="请选择城市">  <Listbox search={false}>    <Input as="search" placeholder="搜索..." />    <MenuItem as="item" value="beijing">北京</MenuItem>    <MenuItem as="item" value="shanghai">上海</MenuItem>    <MenuItem as="item" value="hangzhou">杭州</MenuItem>  </Listbox></Select>

搜索 + 允许自定义输入

<Select placeholder="请选择或输入">  <Listbox search={false}>    <Input as="search" allowCustomValue placeholder="搜索或输入新选项..." />    <MenuItem as="item" value="tag-a">Tag A</MenuItem>    <MenuItem as="item" value="tag-b">Tag B</MenuItem>  </Listbox></Select>

  • Listbox 面板视觉与 QuickAction 对齐,但外层 padding 保持 8px。

  • 搜索框固定在 header;选项滚动区域单独承载 overlay scrollbar。

连续 Hover

continuousHover 默认 true。开启时 Listbox 只使用一个容器级背景层在直接 MenuItem 之间移动,键盘 active 项也复用该层;关闭后恢复 MenuItem 原有的即时 hover / active 背景。disabled、filtered 项不会成为目标。

Listbox 样式覆盖

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

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

变量 用途与默认参考
--snowui-listbox-background-override 背景(默认值见组件源码)
--snowui-listbox-padding-override padding(默认 var(--padding-2) = 8px)
--snowui-listbox-radius-override border-radius(默认 var(--radius-4) = 16px)
--snowui-menu-item-radius-override border-radius(默认 var(--radius-3) = 12px)

设计指南 · Token · 组合边界

Listbox 类型与状态

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

可双向绑定:本组件未声明具名双向绑定属性;状态更新通过公开回调或所属宿主处理。

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

共享 Frame API · 返回 API

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

ListboxItem

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

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

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}`;