Combobox
特性
- 支持受控或非受控模式。
- 提供两种定位模式。
- 支持项目、标签、项目组。
- 完全管理焦点。
- 完整的键盘导航。
- 支持自定义占位符。
- 支持从右到左方向。
安装
从命令行安装组件。
$ npm add reka-ui结构
导入所有部分并将它们组合在一起。
<script setup lang="ts">
import {
ComboboxAnchor,
ComboboxArrow,
ComboboxCancel,
ComboboxContent,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxLabel,
ComboboxPortal,
ComboboxRoot,
ComboboxSeparator,
ComboboxTrigger,
ComboboxViewport,
} from 'reka-ui'
</script>
<template>
<ComboboxRoot>
<ComboboxAnchor>
<ComboboxInput />
<ComboboxTrigger />
<ComboboxCancel />
</ComboboxAnchor>
<ComboboxPortal>
<ComboboxContent>
<ComboboxViewport>
<ComboboxItem>
<ComboboxItemIndicator />
</ComboboxItem>
<ComboboxGroup>
<ComboboxLabel />
<ComboboxItem>
<ComboboxItemIndicator />
</ComboboxItem>
</ComboboxGroup>
<ComboboxSeparator />
</ComboboxViewport>
<ComboboxArrow />
</ComboboxContent>
</ComboboxPortal>
</ComboboxRoot>
</template>API 参考
Root
包含组合框的所有部分。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
by | string | ((a: AcceptableValue, b: AcceptableValue) => boolean)Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared. | |
defaultOpen | booleanThe open state of the combobox when it is initially rendered. | |
defaultValue | AcceptableValue | AcceptableValue[]The value of the listbox when initially rendered. Use when you do not need to control the state of the Listbox | |
dir | 'ltr' | 'rtl'The reading direction of the listbox when applicable. | |
disabled | booleanWhen | |
highlightOnHover | true | booleanWhen |
ignoreFilter | booleanWhen | |
modelValue | AcceptableValue | AcceptableValue[]The controlled value of the listbox. Can be binded with with | |
multiple | booleanWhether multiple options can be selected or not. | |
name | stringThe name of the field. Submitted with its owning form as part of a name/value pair. | |
open | booleanThe controlled open state of the Combobox. Can be binded with with | |
openOnClick | false | booleanWhether to open the combobox when the input is clicked |
openOnFocus | false | booleanWhether to open the combobox when the input is focused |
required | booleanWhen | |
resetModelValueOnClear | false | booleanWhen |
resetSearchTermOnBlur | true | booleanWhether to reset the searchTerm when the Combobox input blurred |
resetSearchTermOnSelect | true | booleanWhether to reset the searchTerm when the Combobox value is selected |
| Emit | Payload |
|---|---|
highlight | [payload: { ref: HTMLElement; value: AcceptableValue; }]Event handler when highlighted element changes. |
update:modelValue | [value: AcceptableValue]Event handler called when the value changes. |
update:open | [value: boolean]Event handler called when the open state of the combobox changes. |
| Slots (default) | Payload |
|---|---|
open | booleanCurrent open state |
modelValue | AcceptableValue | AcceptableValue[]Current active value |
Anchor
如果将 ComboboxContent 的位置设置为 popper,则用作锚点。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
reference | ReferenceElementThe reference (or anchor) element that is being referred to for positioning. If not provided will use the current component as anchor. |
Input
用于搜索组合框项目的输入组件。
| Prop | Default | Type |
|---|---|---|
as | 'input' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
autoFocus | booleanFocus on element when mounted. | |
disabled | booleanWhen | |
displayValue | ((val: any) => string)The display value of input for selected item. Does not work with | |
modelValue | stringThe controlled value of the filter. Can be binded with with v-model. |
| Emit | Payload |
|---|---|
update:modelValue | [string]Event handler called when the value changes. |
Trigger
切换组合框内容的按钮。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
Cancel
清除搜索词的按钮。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Empty
当没有项目匹配查询时显示。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Portal
使用此组件时,将内容部分传送到 body 中。
您需要为 ComboboxContent 设置 position="popper",以确保位置自动计算,类似于 Popover 或 DropdownMenu。
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Content
组合框打开时弹出的组件。
| Prop | Default | Type |
|---|---|---|
align | 'start' | 'center' | 'end'The preferred alignment against the trigger. May change when collisions occur. | |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | booleanWhen | |
bodyLock | booleanThe document.body will be lock, and scrolling will be disabled. | |
collisionBoundary | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. | |
collisionPadding | number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. | |
disableOutsidePointerEvents | booleanWhen | |
disableUpdateOnLayoutShift | booleanWhether to disable the update position for the content when the layout shifted. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
position | 'inline' | 'popper'The positioning mode to use, | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
side | 'top' | 'right' | 'bottom' | 'left'The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. | |
sideFlip | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'partial' | 'always'The sticky behavior on the align axis. | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Emit | Payload |
|---|---|
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent]Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS Variable | Description |
|---|---|
--reka-combobox-content-transform-origin | 从内容和箭头位置/偏移计算出的 transform-origin。仅在 position="popper" 时存在。 |
--reka-combobox-content-available-width | 触发器和边界边缘之间的剩余宽度。仅在 position="popper" 时存在。 |
--reka-combobox-content-available-height | 触发器和边界边缘之间的剩余高度。仅在 position="popper" 时存在。 |
--reka-combobox-trigger-width | 触发器的宽度。仅在 position="popper" 时存在。 |
--reka-combobox-trigger-height | 触发器的高度。仅在 position="popper" 时存在。 |
Viewport
包含所有项目的滚动视口。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
nonce | stringWill add |
Item
包含组合框项目的组件。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
textValue | stringA string representation of the item contents. If the children are not plain text, then the | |
value* | AcceptableValueThe value given as data when submitted with a |
| Emit | Payload |
|---|---|
select | [event: SelectEvent<AcceptableValue>]Event handler called when the selecting item. |
| Data Attribute | Value |
|---|---|
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
ItemIndicator
当项目被选中时渲染。您可以直接设置此元素的样式,或将其用作包装器来放置图标,或两者兼而有之。
| Prop | Default | Type |
|---|---|---|
as | 'span' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Group
用于分组多个项目。与 ComboboxLabel 结合使用,通过自动标记确保良好的可访问性。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Label
用于渲染组的标签。无法使用箭头键聚焦。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
for | string |
Separator
用于在组合框中视觉上分隔项目。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Arrow
一个可选的箭头元素,与内容一起渲染。这可用于帮助视觉上连接触发器与 ComboboxContent。必须在 ComboboxContent 内部渲染。仅当 position 设置为 popper 时可用。
| Prop | Default | Type |
|---|---|---|
as | 'svg' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | 5 | numberThe height of the arrow in pixels. |
rounded | booleanWhen | |
width | 10 | numberThe width of the arrow in pixels. |
Virtualizer
虚拟容器,用于实现列表虚拟化。
组合框项目在传递给虚拟化器之前必须手动过滤。请参见下面的示例。
有关虚拟化的更多一般信息,请参阅虚拟化指南。
| Prop | Default | Type |
|---|---|---|
estimateSize | numberEstimated size (in px) of each item | |
options* | AcceptableValue[]List of items | |
overscan | numberNumber of items rendered outside the visible area | |
textContent | ((option: AcceptableValue) => string)Text content for each item to achieve type-ahead feature |
| Slots (default) | Payload |
|---|---|
option | null | string | number | bigint | Record<string, any> |
virtualizer | Virtualizer<HTMLElement, Element> |
virtualItem | VirtualItem |
示例
绑定对象作为值
与仅允许提供字符串作为值的原生 HTML 表单控件不同,reka-ui 还支持绑定复杂对象。
确保设置 displayValue 属性以在选择项目时设置输入值。
<script setup lang="ts">
import { ComboboxContent, ComboboxInput, ComboboxItem, ComboboxPortal, ComboboxRoot } from 'reka-ui'
import { ref } from 'vue'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
const selectedPeople = ref(people[0])
</script>
<template>
<ComboboxRoot v-model="selectedPeople">
<ComboboxInput :display-value="(v) => v.name" />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem
v-for="person in people"
:key="person.id"
:value="person"
:disabled="person.unavailable"
>
{{ person.name }}
</ComboboxItem>
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>选择多个值
Combobox 组件允许您选择多个值。您可以通过提供一个值数组而不是单个值来启用此功能。
<script setup lang="ts">
import { ComboboxRoot } from 'reka-ui'
import极速 {
ref
} from 'vue'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: '极速 Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
const selectedPeople = ref([people[0], people[1]])
</script>
<template>
<ComboboxRoot
v-model="selectedPeople"
multiple
>
…
</ComboboxRoot>
</template>自定义过滤
在内部,ComboboxRoot 会根据渲染的文本过滤项目。
但是,您也可以提供自己的自定义过滤逻辑,同时设置 ignoreFilter="true"。
<script setup lang="ts">
import { ComboboxContent, ComboboxInput, ComboboxItem, ComboboxPortal, ComboboxRoot, useFilter } from 'reka-ui'
import { ref } from 'vue'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
const selectedPeople = ref(people[0])
const searchTerm = ref('')
const { startsWith } = useFilter({ sensitivity: 'base' })
const filteredPeople = computed(() => people.filter(p => startsWith(p.name, searchTerm.value)))
</script>
<极速
<ComboboxRoot
v-model="selectedPeople"
:ignore-filter="true"
>
<ComboboxInput v-model="searchTerm" />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem
v-for="person in filteredPeople"
:key="person.id"
:value="person"
>
{{ person.name }}
</ComboboxItem>
</ComboboxContent>
</ComboboxPortal>
</ComboboxRoot>
</template>自定义标签
默认情况下,Combobox 会使用输入内容作为屏幕阅读器的标签。如果您想更好地控制向辅助技术宣布的内容,请使用 Label 组件。
<script setup lang="ts">
import { ComboboxInput, ComboboxRoot, Label } from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot v-model="selectedPeople">
<Label for="person">Person: </Label>
<ComboboxInput
id="person"
placeholder="Select a person"
/>
…
</ComboboxRoot>
</template>禁用项目
您可以通过 data- disabled 属性为禁用的项目添加特殊样式。
<script setup lang="ts">
import {
ComboboxContent,
ComboboxInput,
ComboboxItem,
ComboboxPortal,
ComboboxRoot,
} from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot>
<ComboboxInput />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem
class="ComboboxItem"
disabled
>
…
</Combobox极速
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>/* styles.css */
.ComboboxItem[data-disabled] {
color: "gainsboro";
}使用分隔符
使用 Separator 部分在项目之间添加分隔符。
<script setup lang="ts">
import {
ComboboxContent,
ComboboxInput,
ComboboxItem,
ComboboxPortal,
ComboboxRoot,
ComboboxSeparator
} from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot>
<ComboboxInput />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem>…</ComboboxItem>
<ComboboxItem>…</ComboboxItem>
<ComboboxItem>…</ComboboxItem>
<ComboboxSeparator />
<ComboboxItem>…</ComboboxItem>
<ComboboxItem>…</Combobox极速
<ComboboxItem>…</ComboboxItem>
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>分组项目
使用 Group 和 Label 部分将项目分组到一个部分中。
<script setup lang="ts">
import {
ComboboxContent,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxLabel,
ComboboxPortal,
ComboboxRoot
} from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot>
<ComboboxInput />
<ComboboxPortal>
<ComboboxContent>
<ComboboxGroup>
<ComboboxLabel>Label</ComboboxLabel>
<ComboboxItem>…</ComboboxItem>
<ComboboxItem>…</ComboboxItem>
<ComboboxItem>…</ComboboxItem>
</ComboboxGroup>
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>复杂项目
您可以在项目中使用自定义内容。
<script setup lang="ts">
import {
ComboboxContent,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxLabel,
ComboboxPortal,
ComboboxRoot
} from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot>
<ComboboxInput />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem>
<img src="…">
Adolfo Hess
<ComboboxItemIndicator />
</ComboboxItem>
…
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>阻止选择行为
默认情况下,选择 ComboboxItem 会关闭内容,并使用提供的值更新 modelValue。 您可以通过阻止默认的 @select.prevent 来阻止此行为。
<script setup lang="ts">
import { ComboboxContent, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxPortal, ComboboxRoot } from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<ComboboxRoot>
<ComboboxInput />
<ComboboxPortal>
<ComboboxContent>
<ComboboxItem @select.prevent>
Item A
</ComboboxItem>
…
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>具有工作过滤功能的虚拟化组合框
组合框项目必须在传递给虚拟化器之前手动过滤。
有关虚拟化的更多一般信息,请参阅虚拟化指南。
<script setup lang="ts">
import { ComboboxContent, ComboboxInput, ComboboxItem, ComboboxPortal, ComboboxRoot, ComboboxViewport, ComboboxVirtualizer, useFilter } from 'reka-ui'
import { computed, ref } from 'vue'
const people = Array.from({ length: 100000 }).map((_, id) => ({ id, name: `Person #${id}` }))
const selectedPeople = ref(people[0])
const searchTerm = ref('')
const { contains } = useFilter({ sensitivity: 'base' })
const filteredPeople = computed(() => people.filter(p => contains(p.name, searchTerm.value)))
</script>
<template>
<ComboboxRoot v-model="selectedPeople">
<ComboboxInput v-model="searchTerm" />
<ComboboxPortal>
<ComboboxContent class="max-h-[40vh] overflow-hidden">
<ComboboxViewport>
<ComboboxVirtualizer
v-slot="{ option }"
:options="filteredPeople"
:text-content="(x) => x.name"
:estimate-size="24"
>
<ComboboxItem :value="option">
{{ option.name }}
</ComboboxItem>
</ComboboxVirtualizer>
</ComboboxViewport>
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template>可访问性
更多信息,请参阅 W3C Combobox Autocomplete List 示例。
键盘交互
| Key | Description |
|---|---|
Enter | 当焦点在 ComboboxItem 上时,选择聚焦的项目。 |
ArrowDown | 当焦点在 ComboboxInput 上时,打开组合框内容。当焦点在项目上时,将焦点移动到下一个项目。 |
ArrowUp | 当焦点在 Combobox极速 上时,打开组合框内容。当焦点在项目上时,将焦点移动到上一个项目。 |
Esc | 关闭组合框并在 ComboboxInput 字段中恢复选定的项目。 |
自定义 API
通过将原始部分抽象到您自己的组件中,创建您自己的 API。
命令菜单
组合框可用于构建您自己的命令菜单。
用法
<script setup lang="ts">
import { Command, CommandItem } from './your-command'
</script>
<template>
<Command>
<CommandItem value="1">
Item 1
</CommandItem>
<CommandItem value="2">
Item 2
</CommandItem>
<CommandItem value="3">
Item 3
</CommandItem>
</Command>
</template>实现
// your-command.ts
export { default as Command } from 'Command.vue'
export { default as CommandItem } from 'CommandItem.vue'<!-- Command.vue -->
<script setup lang极速
import type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, } from '@radix-icons/vue'
import { ComboboxContent, ComboboxInput, ComboboxPortal, ComboboxRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<ComboboxRootProps>()
const emits = defineEmits<ComboboxRootEmits>()
const forward = useForwardPropsEmits(props, emits)
</script>
<template>
<ComboboxRoot
v-bind="forward"
:open="true"
model-value=""
>
<ComboboxInput placeholder="Type a command or search…" />
<ComboboxPortal>
<ComboboxContent
@escape-key-down.prevent
@focus-outside.prevent
@interact-outside.prevent
@pointer-down-outside.prevent
>
<ComboboxViewport>
<slot />
</ComboboxViewport>
</ComboboxContent>
</ComboboxPortal>
ComboboxRoot>
</template><!-- ComboboxItem.vue -->
<script setup lang="ts">
import type { ComboboxItemProps } from 'reka-ui'
import { CheckIcon } from '@radix-icons/v极速
import { ComboboxItem } from 'reka-ui'
const props = defineProps<ComboboxItemProps>()
</script>
<template>
<ComboboxItem
v-bind="props"
@select.prevent
>
<slot />
</ComboboxItem>
</template>