跳转到内容

Listbox 列表框 ​

用于展示一组常驻选项,并允许用户选择其中一项或多项。

用法 ​

从成员列表中选择一项

团队成员
林知夏lin@xihan.dev
陈望舒chen@xihan.dev
周予安zhou@xihan.dev

组件结构 ​

加粗的是必需部件。

data-scope="listbox":root · label · content · item · item-prefix · item-text · item-description · item-suffix · item-indicator · group · group-label · empty · loading · load-more-trigger

示例 ​

多选 ​

允许选择多个选项

参与团队
产品设计
工程研发
市场运营
客户支持

分组 ​

按类别组织选项

城市
亚洲
Bangkok 曼谷
Beijing 北京
Chengdu 成都
欧洲
Berlin 柏林
London 伦敦

滚动 ​

固定高度显示长列表

播放列表
曲目 01
曲目 02
曲目 03
曲目 04
曲目 05
曲目 06
曲目 07
曲目 08
曲目 09
曲目 10
曲目 11
曲目 12

空态 ​

没有选项时显示简洁提示

团队成员
暂无成员

设计指引 ​

何时使用 ​

  • 选项需要常驻可见。
  • 需要单选、多选或连续范围选择。

何时不用 ​

  • 选项需要收起:使用选择器。
  • 内容不可选择:使用列表。

特性 ​

  • 支持 single、multiple 和 extended 三种选择模式。
  • 支持方向键导航、连续输入检索与范围选择。
  • 支持分组、禁用条目和定高滚动。
  • 条目可逐条声明语气,失效或需要留意的那条自带该族字色与高亮底。
  • 条目可写副文本,第 2 行放一句解释,与标题同列、走 muted 档。
  • 行首与行尾两格各有逐条钩子:只想加个图标或计数,不必把整条重搭。
  • 提供空态、加载态与加载更多部件。

组合 ​

最佳实践 ​

  • 使用 item-indicator 表示选中,并始终保留其空间。选中与下拉候选同一种读法:行不换面、不换字色,只在行尾画对号。
  • 条目标题保持简短,补充信息使用次级文字。
  • 长列表设置固定高度,并按需启用虚拟化。
  • 空态与加载态放在 content 外,与其互斥显示。

反模式 ​

  • 用选项承载删除、提交等即时命令。
  • 在没有可见选项时保留空白列表边框。

API 参考 ​

产物 ​

层值
自定义元素<xh-listbox>
Vue 组件XhListboxContent XhListboxEmpty XhListboxGroup XhListboxGroupLabel XhListboxItem XhListboxItemDescription XhListboxItemIndicator XhListboxItemPrefix XhListboxItemSuffix XhListboxItemText XhListboxLabel XhListboxLoadMoreTrigger XhListboxLoading XhListboxRoot
组合式函数useListbox
状态机listboxMachine
皮肤@xihan-ui/styles/listbox.css

Props ​

属性类型必填说明
collectionListboxNode[]条目数据,显示文本与禁用的事实源。提供后条目部件只需声明 value。 未提供时回到文本与禁用都写在条目部件上的方式。
valuestring | string[]选中值,提供即受控;单选可写为裸串,内部归一为数组。
defaultValuestring | string[]
selectionModeListboxSelectionMode选择模式,默认 single。
disabledboolean整个列表禁用,键盘与点击都不再改选中值。
readOnlyboolean只读:条目照常浏览与聚焦,但选中值不可修改。禁用则连同焦点一起退出。
loadingboolean条目加载中:列表报告 aria-busy,显示在途占位,隐藏空态占位。
invalidboolean校验失败:列表报告 aria-invalid,各角色节点带 data-invalid。
toneTone语气:brand / neutral / success / warning / danger / info,决定勾选标记使用哪族颜色。
sizeSize尺寸:sm / md / lg,决定条目的几何档位。
loopboolean方向键到达末尾是否回绕,默认 true。
dirDirection文字方向,默认 ltr。
orientationOrientation方向键轴向,默认 vertical。
typeaheadboolean连打检索,默认开启。
onValueChange(details: ListboxValueChangeDetails) => voidvalue 变化意图回调。

ListboxNode ​

collection 的元素。

字段类型必填说明
valuestring是
labelstring展示文本,也是连打检索的取字来源;默认回退为 value。
disabledboolean条目禁用:方向键跳过它,但它仍可聚焦、仍是导航起点。
toneTone该条选项自身的性质:危险选项写 danger、需要留意的写 warning。不写即与其余条目同档。 只换字色与悬停 / 按下的面,不表达选中与校验;选中的标记与禁用都压过它。 彩字不是唯一通道,要紧的差别仍要配图标或文案。整列的 tone 不下发给条目。
descriptionstring副文本,写入 item-description 部件;未提供时本条不铺该部件。 它是第 2 行的说明,跟着条目走 muted 档,不跟语气;放不下一行的解释才用它, 一句话能说清的写进 label。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
value-changeListboxValueChangeDetails选中集合变化;detail 为 { value: string[] }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhListboxRootdefaultListboxRootSlotProps
XhListboxRootlabel—
XhListboxRootitemListboxNodeMeta只填条目的文字槽,副文本与首尾两格照旧各归各的
XhListboxRootitem-prefixListboxNodeMeta只接管行首那一格,其余槽照旧由数据铺
XhListboxRootitem-suffixListboxNodeMeta只接管行尾那一格(计数、徽标、次级图标),其余槽照旧由数据铺

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhListboxGroupvaluestring是
XhListboxItemvaluestring是
XhListboxItemdisabledboolean默认交给 connect 查询 collection,写死 false 会覆盖数据中的禁用。
XhListboxRootlabelReactNode标题文字。提供后不必再写 label 部件。
XhListboxRootrenderItem(node: ListboxNodeMeta) => ReactNode每个条目的自定义内容;未提供时使用 collection 中的 label。
XhListboxRootrenderItemPrefix(node: ListboxNodeMeta) => ReactNode只接管条目行首那一格;其余槽仍由数据铺。
XhListboxRootrenderItemSuffix(node: ListboxNodeMeta) => ReactNode只接管条目行尾那一格;其余槽仍由数据铺。
XhListboxRootchildrenSlotChildren<ListboxRootSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
item'checked' | 'unchecked'
item-prefix'checked' | 'unchecked'
item-text'checked' | 'unchecked'
item-description'checked' | 'unchecked'
item-suffix'checked' | 'unchecked'
item-indicator'checked' | 'unchecked'

以下名称仅用于内部状态机。

状态:idle

事件:VALUE.SET · VALUE.CLEAR · ITEM.SELECT · ITEM.TOGGLE · ITEM.FOCUS · FOCUS.CLEAR · LIST.BLUR · PRESS.START · PRESS.END

判据:canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
valuestring[]选中集合;单选模式下长度 ≤ 1。
collectionreadonly ListboxNodeMeta[]由 collection 推导的条目元信息,按数据顺序排列;未提供 collection 时为空数组。
selectionModeListboxSelectionMode生效的选择模式。
focusedValuestring | null焦点锚点;焦点不在列表内时为 null。
disabledboolean
readOnlyboolean
invalidboolean
loadingboolean
isSelected(value: string) => boolean
setValue(next: string[]) => void
select(value: string) => void只保留该条目;加选使用 toggle。
toggle(value: string) => void
getRootProps() => T['element']
getLabelProps() => T['element']
getContentProps() => T['element']
getEmptyProps() => T['element']空态占位:放在 root 中、content 的兄弟。 提供 collection 时由连接层按条数收放;条目手写时不写 hidden,是否显示由作者决定。
getLoadingProps() => T['element']在途占位:与空态占位同一位置,两者不同时显示:加载期间显示它,空态让位。 提供 collection 时由连接层按条数收放;条目手写时只按 loading 收放。
getLoadMoreTriggerProps() => T['element']取下一页的入口:库不知道是否还有下一页,是否显示与点击后的行为都由作者决定, 连接层只保证取数在途与整列禁用两档不可点击。
getGroupProps(props: ListboxGroupProps) => T['element']
getGroupLabelProps(props: ListboxGroupProps) => T['element']
getItemProps(props: ListboxItemProps) => T['element']
getItemPrefixProps(props: ListboxItemProps) => T['element']
getItemTextProps(props: ListboxItemProps) => T['element']
getItemDescriptionProps(props: ListboxItemProps) => T['element']
getItemSuffixProps(props: ListboxItemProps) => T['element']
getItemIndicatorProps(props: ListboxItemProps) => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Tab / Shift+Tabfocus outside the listbox整个列表只占一个 Tab 位:焦点进入锚点条目,无锚点时先落容器再由它转投
ArrowDownfocus in listbox, orientation=vertical焦点移到下一个可停留条目(禁用项跳过、尽头按 loop 回绕);orientation=horizontal 时改由 ArrowRight 承担,dir=rtl 再对调左右
ArrowUpfocus in listbox, orientation=vertical焦点移到上一个可停留条目(禁用项跳过、尽头按 loop 回绕);orientation=horizontal 时改由 ArrowLeft 承担,dir=rtl 再对调左右
Homefocus in listbox焦点移到首个可停留条目
Endfocus in listbox焦点移到末个可停留条目
Enter / Spacefocus on item, selectionMode 为 single 或 extended只选中焦点条目,替换原有选中;条目自报禁用则不认
Space / Enter / Ctrl+Spacefocus on item, 可多选(multiple;extended 下须按住 Ctrl/Cmd)切换焦点条目的选中态,其余选中不动
Shift+ArrowDown / Shift+ArrowUpfocus in listbox, 可多选焦点移到相邻条目并切换它的选中态;反向移动即取消刚扩展进来的条目
Ctrl+A / Cmd+Afocus in listbox, 可多选选中全部可选条目;已经全选则把它们一并取消(禁用但已选中的不动)
Enter / Spaceheld in item / load-more-trigger, interactive按住期间该部件投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下;禁用、只读的条目与在途中的取下一页不进
单个可打印字符focus in listbox, typeahead 未关连打检索把焦点移到首字母匹配的条目,不改选中值

ARIA ​

以下属性由 connect 生成。

部件属性值
contentaria-busy'true' | undefined
contentaria-disabled'true' | 'false'
contentaria-invalid'true' | 'false'
contentaria-labelledbylabel 部件的 id
contentaria-multiselectable'true' | 'false'
contentaria-orientationprops.orientation
contentaria-readonly'true' | 'false'
contentrole'listbox'
itemaria-disabled'true' | 'false'
itemaria-selected'true' | 'false'
itemrole'option'
item-prefixaria-hidden'true'
item-indicatoraria-hidden'true'
grouparia-labelledbygroup-label 部件的 id
grouprole'group'

样式参考 ​

皮肤 ​

@xihan-ui/styles/listbox.css 使用 [data-scope="listbox"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-disabled''(条件成立时才出现)
rootdata-invalid''(条件成立时才出现)
rootdata-loading''(条件成立时才出现)
rootdata-orientationprops.orientation
rootdata-readonly''(条件成立时才出现)
rootdata-sizeprops.size
rootdata-toneprops.tone
labeldata-disabled''(条件成立时才出现)
contentdata-disabled''(条件成立时才出现)
contentdata-invalid''(条件成立时才出现)
contentdata-orientationprops.orientation
contentdata-readonly''(条件成立时才出现)
itemdata-disabled''(条件成立时才出现)
itemdata-highlighted''(条件成立时才出现)
itemdata-pressed''(条件成立时才出现)
itemdata-state'checked' | 'unchecked'
itemdata-tonemetaOf.get(item.value)?.tone
itemdata-xh-collection-context'overlay'
itemdata-xh-collection-item''
itemdata-xh-collection-sizeprops.size
item-prefixdata-disabled''(条件成立时才出现)
item-prefixdata-highlighted''(条件成立时才出现)
item-prefixdata-state'checked' | 'unchecked'
item-prefixdata-xh-collection-slot'prefix'
item-textdata-disabled''(条件成立时才出现)
item-textdata-highlighted''(条件成立时才出现)
item-textdata-state'checked' | 'unchecked'
item-textdata-xh-collection-slot'text'
item-descriptiondata-disabled''(条件成立时才出现)
item-descriptiondata-highlighted''(条件成立时才出现)
item-descriptiondata-state'checked' | 'unchecked'
item-descriptiondata-xh-collection-slot'description'
item-suffixdata-disabled''(条件成立时才出现)
item-suffixdata-highlighted''(条件成立时才出现)
item-suffixdata-state'checked' | 'unchecked'
item-suffixdata-xh-collection-slot'suffix'
item-indicatordata-disabled''(条件成立时才出现)
item-indicatordata-highlighted''(条件成立时才出现)
item-indicatordata-state'checked' | 'unchecked'
item-indicatordata-xh-collection-slot'indicator'
groupdata-disabled''(条件成立时才出现)
group-labeldata-disabled''(条件成立时才出现)
emptydata-disabled''(条件成立时才出现)
loadingdata-disabled''(条件成立时才出现)
load-more-triggerdata-disabled''(条件成立时才出现)
load-more-triggerdata-loading''(条件成立时才出现)
load-more-triggerdata-pressed''(条件成立时才出现)
load-more-triggerdata-xh-action-control''
load-more-triggerdata-xh-action-display'always'
load-more-triggerdata-xh-action-profile'row'
load-more-triggerdata-xh-action-sizeprops.size
load-more-triggerdata-xh-action-variant'ghost'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-listbox-content-bgcontentbackgrounddefault--xh-bg-surfacelistbox 的 content 部件 background 覆盖槽。
--xh-listbox-content-bordercontentborderdefault--xh-border-defaultlistbox 的 content 部件 border 覆盖槽。
--xh-listbox-content-border-invalidcontentborder-colorinvalid--xh-border-invalidlistbox 的 content 部件 border-color 覆盖槽。
--xh-listbox-content-fgcontentcolordefault--xh-fg-defaultlistbox 的 content 部件 color 覆盖槽。
--xh-listbox-content-gapcontentgapdefault--xh-list-option-gaplistbox 的 content 部件 gap 覆盖槽。
--xh-listbox-content-max-hcontentmax-block-sizedefault--xh-viewport-h-mdlistbox 的 content 部件 max-block-size 覆盖槽。
--xh-listbox-content-pxcontentpadding-inlinedefault--xh-space-1listbox 的 content 部件 padding-inline 覆盖槽。
--xh-listbox-content-pycontentpadding-blockdefault--xh-space-1listbox 的 content 部件 padding-block 覆盖槽。
--xh-listbox-content-radiuscontentborder-radiusdefault--xh-shape-surfacelistbox 的 content 部件 border-radius 覆盖槽。
--xh-listbox-empty-fgemptycolordefault--xh-fg-subtlelistbox 的 empty 部件 color 覆盖槽。
--xh-listbox-empty-font-sizeemptyfont-sizedefault--xh-_listbox-font-sizelistbox 的 empty 部件 font-size 覆盖槽。
--xh-listbox-empty-pxemptypadding-inlinedefault--xh-_listbox-item-pxlistbox 的 empty 部件 padding-inline 覆盖槽。
--xh-listbox-empty-pyemptypadding-blockdefault--xh-space-3listbox 的 empty 部件 padding-block 覆盖槽。
--xh-listbox-gaprootgapdefault--xh-space-2listbox 的 root 部件 gap 覆盖槽。
--xh-listbox-group-gapgroupgapdefault--xh-list-option-gaplistbox 的 group 部件 gap 覆盖槽。
--xh-listbox-group-label-fggroup-labelcolordefault--xh-fg-subtlelistbox 的 group-label 部件 color 覆盖槽。
--xh-listbox-group-label-font-sizegroup-labelfont-sizedefault--xh-text-caption-sizelistbox 的 group-label 部件 font-size 覆盖槽。
--xh-listbox-group-label-font-weightgroup-labelfont-weightdefault--xh-font-weight-mediumlistbox 的 group-label 部件 font-weight 覆盖槽。
--xh-listbox-group-label-pxgroup-labelpadding-inlinedefault--xh-_listbox-item-pxlistbox 的 group-label 部件 padding-inline 覆盖槽。
--xh-listbox-group-label-pygroup-labelpadding-blockdefault--xh-space-1listbox 的 group-label 部件 padding-block 覆盖槽。
--xh-listbox-group-spacinggroupmargin-block-starthas([data-scope='listbox'][data-part='item']:not([hidden])
not([data-scope='listbox'][data-part='content'] [hidden] *)
--xh-space-1_5listbox 的 group 部件 margin-block-start 覆盖槽。
--xh-listbox-icon-sizeitem
root
--xh-icon-sizedefault
size=lg
size=sm
--xh-_collection-glyph-size
--xh-glyph-size-lg
--xh-glyph-size-md
--xh-glyph-size-sm
listbox 的 item、root 部件 --xh-icon-size 覆盖槽。
--xh-listbox-item-bg-hoveritembackground-colordisabled
error
highlighted
hover
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
selected
xh-collection-context=overlay
--xh-bg-subtlelistbox 的 item 部件 background-color 覆盖槽。
--xh-listbox-item-bg-presseditembackground-colordisabled
error
is(:active, [data-pressed])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
xh-collection-context=overlay
--xh-bg-subtle-hoverlistbox 的 item 部件 background-color 覆盖槽。
--xh-listbox-item-check-fgitemcolordisabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
state=checked
xh-collection-context=overlay
xh-collection-slot=indicator
--xh-listbox-item-indicator-fglistbox 的 item 部件 color 覆盖槽。
--xh-listbox-item-fgitemcolordefault
disabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
xh-collection-context=overlay
--xh-fg-defaultlistbox 的 item 部件 color 覆盖槽。
--xh-listbox-item-fg-selecteditemcolordisabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
xh-collection-context=overlay
--xh-listbox-item-fglistbox 的 item 部件 color 覆盖槽。
--xh-listbox-item-font-sizeitemfont-sizedefault--xh-_listbox-font-sizelistbox 的 item 部件 font-size 覆盖槽。
--xh-listbox-item-font-weight-selecteditemfont-weightdisabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
xh-collection-context=overlay
--xh-font-weight-regularlistbox 的 item 部件 font-weight 覆盖槽。
--xh-listbox-item-gapitemmargin-inline-end
margin-inline-start
xh-collection-slot=indicator
xh-collection-slot=prefix
xh-collection-slot=shortcut
xh-collection-slot=suffix
--xh-_listbox-gaplistbox 的 item 部件 margin-inline-end、margin-inline-start 覆盖槽。
--xh-listbox-item-indicator-fgitemcolordisabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
selected
state=checked
xh-collection-context=overlay
xh-collection-slot=indicator
--xh-_listbox-accentlistbox 的 item 部件 color 覆盖槽。
--xh-listbox-item-indicator-sizeitem-indicator--xh-icon-size
block-size
inline-size
default--xh-control-indicator-sizelistbox 的 item-indicator 部件 --xh-icon-size、block-size、inline-size 覆盖槽。
--xh-listbox-item-leadingitemline-heightdefault--xh-leading-normallistbox 的 item 部件 line-height 覆盖槽。
--xh-listbox-item-pxitempadding-inlinedefault--xh-_listbox-item-pxlistbox 的 item 部件 padding-inline 覆盖槽。
--xh-listbox-item-pyitempadding-blockdefault--xh-_listbox-item-pylistbox 的 item 部件 padding-block 覆盖槽。
--xh-listbox-item-radiusitemborder-radiusdefault--xh-shape-controllistbox 的 item 部件 border-radius 覆盖槽。
--xh-listbox-label-fglabelcolordefault--xh-fg-mutedlistbox 的 label 部件 color 覆盖槽。
--xh-listbox-label-font-sizelabelfont-sizedefault--xh-text-label-sizelistbox 的 label 部件 font-size 覆盖槽。
--xh-listbox-label-font-weightlabelfont-weightdefault--xh-text-label-weightlistbox 的 label 部件 font-weight 覆盖槽。
--xh-listbox-load-more-trigger-bg-hoverload-more-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverlistbox 的 load-more-trigger 部件 background-color 覆盖槽。
--xh-listbox-load-more-trigger-fgload-more-triggercolordefault
disabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_tone-fglistbox 的 load-more-trigger 部件 color 覆盖槽。
--xh-listbox-load-more-trigger-font-sizeload-more-triggerfont-sizedefault--xh-_listbox-font-sizelistbox 的 load-more-trigger 部件 font-size 覆盖槽。
--xh-listbox-load-more-trigger-gapload-more-triggergapdefault--xh-_listbox-gaplistbox 的 load-more-trigger 部件 gap 覆盖槽。
--xh-listbox-load-more-trigger-pxload-more-triggerpadding-inlinedefault--xh-_listbox-item-pxlistbox 的 load-more-trigger 部件 padding-inline 覆盖槽。
--xh-listbox-load-more-trigger-pyload-more-triggerpadding-blockxh-action-profile=row--xh-_listbox-item-pylistbox 的 load-more-trigger 部件 padding-block 覆盖槽。
--xh-listbox-load-more-trigger-radiusload-more-triggerborder-radiusdefault--xh-shape-controllistbox 的 load-more-trigger 部件 border-radius 覆盖槽。
--xh-listbox-loading-fgloadingcolordefault--xh-fg-subtlelistbox 的 loading 部件 color 覆盖槽。
--xh-listbox-loading-font-sizeloadingfont-sizedefault--xh-_listbox-font-sizelistbox 的 loading 部件 font-size 覆盖槽。
--xh-listbox-loading-pxloadingpadding-inlinedefault--xh-_listbox-item-pxlistbox 的 loading 部件 padding-inline 覆盖槽。
--xh-listbox-loading-pyloadingpadding-blockdefault--xh-space-3listbox 的 loading 部件 padding-block 覆盖槽。

动效 ​

动效角色:按压 · 状态(见动效规范)。

本组件皮肤不含过渡与关键帧,也没有脚本驱动的动效:状态一变,外观立即到位。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License