下拉菜单(自动完成)

下拉菜单是您应用程序中可访问的自动完成和命令面板的基础,具有对键盘导航的强大支持。

要开始使用,请通过 npm 安装无头 UI。

请注意,此库仅支持 Vue 3

npm install @headlessui/vue

下拉菜单是使用 ComboboxComboboxInputComboboxButtonComboboxOptionsComboboxOptionComboboxLabel 组件构建的。

ComboboxInput 将在搜索时自动打开/关闭 ComboboxOptions

您完全负责如何过滤结果,无论是使用模糊搜索库在客户端还是通过向 API 发出服务器端请求。在本示例中,我们将为了演示目的而使逻辑保持简单。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person" :value="person" > {{ person }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ 'Durward Reynolds', 'Kenton Towne', 'Therese Wunsch', 'Benedict Kessler', 'Katelyn Rohan', ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

在前面的示例中,我们使用 string 值列表作为数据,但您也可以使用包含其他信息的 对象。唯一的限制是您必须为输入提供 displayValue。这很重要,以便可以在 ComboboxInput 中渲染对象的基于字符串的版本。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value"
:displayValue="(person) => person.name"
/>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" :disabled="person.unavailable" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: false }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

无头 UI 会跟踪每个组件的许多状态,例如当前选择的哪个下拉菜单选项,弹出窗口是打开还是关闭,或者通过键盘当前激活的哪个下拉菜单项。

但由于这些组件是无头的,并且在开箱即用时完全没有样式,因此您在提供每个状态所需的样式之前,无法在 UI 中看到这些信息。

每个组件都通过 插槽道具 公开有关其当前状态的信息,您可以使用这些信息有条件地应用不同的样式或渲染不同的内容。

例如,ComboboxOption 组件公开了 active 状态,它告诉您该项是否当前通过鼠标或键盘处于焦点状态。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Use the `active` state to conditionally style the active option. --> <!-- Use the `selected` state to conditionally style the selected option. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" as="template"
v-slot="{ active, selected }"
>
<li :class="{
'bg-blue-500 text-white': active,
'bg-white text-black': !active,
}"
>
<CheckIcon v-show="selected" />
{{ person.name }} </li> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' 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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

有关所有可用插槽道具的完整列表,请参阅 组件 API 文档

每个组件还通过 data-headlessui-state 属性公开有关其当前状态的信息,您可以使用这些信息有条件地应用不同的样式。

插槽道具 API 中的任何状态为 true 时,它们将在该属性中列为以空格分隔的字符串,因此您可以使用 CSS 属性选择器[attr~=value] 的形式对其进行定位。

例如,以下是 ComboboxOptions 组件以及一些子 ComboboxOption 组件在下拉菜单打开且第二个项目为 active 时渲染的内容

<!-- Rendered `ComboboxOptions` --> <ul data-headlessui-state="open"> <li data-headlessui-state="">Wade Cooper</li> <li data-headlessui-state="active selected">Arlene Mccoy</li> <li data-headlessui-state="">Devon Webb</li> </ul>

如果您使用的是 Tailwind CSS,则可以使用 @headlessui/tailwindcss 插件使用修饰符(如 ui-open:*ui-active:*)定位此属性

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
class="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon class="hidden ui-selected:block" />
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' 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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

与仅允许您提供字符串作为值的原生 HTML 表单控件不同,无头 UI 还支持绑定复杂对象。

<template>
<Combobox v-model="selectedPerson">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id"
:value="person"
:disabled="person.unavailable" >
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue'
const people = [
{ id: 1, name: 'Durward Reynolds', unavailable: false },
{ id: 2, name: 'Kenton Towne', unavailable: false },
{ id: 3, name: 'Therese Wunsch', unavailable: false },
{ id: 4, name: 'Benedict Kessler', unavailable: true },
{ id: 5, name: 'Katelyn Rohan', unavailable: false },
]
const selectedPerson = ref(people[1]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) )
</script>

在将对象绑定为值时,重要的是要确保您将 Comboboxvalue 以及相应的 ComboboxOption 同时使用同一个实例,否则它们将无法相等并导致下拉菜单行为不正常。

为了更轻松地使用同一对象的不同实例,您可以使用 by 道具,通过特定字段比较对象,而不是比较对象标识

<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
by="id"
>
<ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in filteredDepartments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue']) const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) ) </script>

您还可以将自己的比较函数传递给 by 道具,如果您希望完全控制如何比较对象

<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
:by="compareDepartments"
>
<ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in departments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue'])
function compareDepartments(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) )
</script>

下拉菜单组件允许您选择多个值。您可以通过提供值数组而不是单个值来启用此功能。

<template>
<Combobox v-model="selectedPeople" multiple>
<ul v-if="selectedPeople.length > 0"> <li v-for="person in selectedPeople" :key="person.id"> {{ person.name }} </li> </ul> <ComboboxInput /> <ComboboxOptions> <ComboboxOption v-for="person in people" :key="person.id" :value="person"> {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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], people[1]]) </script>

当您选择选项时,这将保持下拉菜单处于打开状态,选择选项将在其位置进行切换。

只要添加或删除选项,您的 v-model 绑定就会更新为包含所有选中选项的数组。

默认情况下,Combobox 将使用输入内容作为屏幕阅读器的标签。如果您想更精确地控制向辅助技术宣布的内容,请使用 ComboboxLabel 组件。

<template> <Combobox v-model="selectedPerson">
<ComboboxLabel>Assignee:</ComboboxLabel>
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxLabel, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果您将 name 道具添加到您的下拉菜单,则将呈现隐藏的 input 元素并与您的选中值保持同步。

<template> <form action="/projects/1/assignee" method="post">
<Combobox v-model="selectedPerson" name="assignee">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

这使您可以在原生 HTML <form> 中使用下拉菜单并进行传统的表单提交,就像您的下拉菜单是原生 HTML 表单控件一样。

诸如字符串之类的基本值将被渲染为包含该值的单个隐藏输入,但诸如对象之类的复杂值将使用方括号表示法对名称进行编码,从而编码为多个输入

<input type="hidden" name="assignee[id]" value="1" /> <input type="hidden" name="assignee[name]" value="Durward Reynolds" />

如果您为 Combobox 提供 defaultValue 道具而不是 value,无头 UI 将为您在内部跟踪其状态,允许您将其用作 不受控组件

<template> <form action="/projects/1/assignee" method="post">
<Combobox name="assignee" :defaultValue="people[0]">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

当使用 HTML 表单 或使用通过 FormData 收集其状态而不是使用 React 状态跟踪其状态的表单 API 时,这可以简化您的代码。

您提供的任何 @update:modelValue 道具仍然会在组件的值发生更改时被调用,以防您需要运行任何副作用,但您无需使用它来自己跟踪组件的状态。

您可以允许用户输入列表中不存在的自己的值,方法是根据 query 值包含一个动态 ComboboxOption

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions>
<ComboboxOption v-if="queryPerson" :value="queryPerson">
Create "{{ query }}"
</ComboboxOption>
<ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('')
const queryPerson = computed(() => {
return query.value === '' ? null : { id: null, name: query.value }
})
const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) )
</script>

根据您正在构建的内容,有时将有关活动选项的附加信息渲染到 <ComboboxOptions> 之外可能很有意义。例如,在命令面板的上下文中预览活动选项。在这些情况下,您可以读取 activeOption 插槽道具参数以访问此信息。

<template>
<Combobox v-model="selectedPerson" v-slot="{ activeOption }">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions>
<div v-if="activeOption">
The current active user is: {{ activeOption.name }}
</div>
</Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

activeOption 将是当前活动 ComboboxOptionvalue

默认情况下,您的 ComboboxOptions 实例将根据 Combobox 组件本身内部跟踪的 open 状态自动显示/隐藏。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- By default, the `ComboboxOptions` will automatically show/hide when typing in the `ComboboxInput`, or when pressing the `ComboboxButton`. --> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果您希望自己处理此问题(可能是因为您需要为此添加额外的包装元素),您可以将 static 道具添加到 ComboboxOptions 实例中,告诉它始终渲染,并检查 Combobox 提供的 open 插槽道具,以控制自己显示/隐藏哪个元素。

<template>
<Combobox v-model="selectedPerson" v-slot="{ open }">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" />
<div v-show="open">
<!-- Using the `static` prop, the `ComboboxOptions` are always rendered and the `open` state is ignored. -->
<ComboboxOptions static>
<ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </div> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

使用 disabled 道具禁用 ComboboxOption。这将使其无法通过鼠标和键盘选择,并且在按向上/向下箭头时将跳过它。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Disabled options will be skipped by keyboard navigation. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
:disabled="person.unavailable"
>
<span :class='{ "opacity-75": person.unavailable }'> {{ person.name }} </span> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

默认情况下,在您在下拉菜单中选择值后,无法将下拉菜单清除回空值 — 当您清除输入并离开元素时,该值会返回到先前选中的值。

如果您希望在下拉菜单中支持空值,请使用 nullable 道具。

<template>
<Combobox v-model="selectedPerson" nullable>
<ComboboxInput @change="query = $event.target.value"
:displayValue="(person) => person?.name"
/>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

使用 nullable 道具时,清除输入并从元素中导航将更新您的 v-model 绑定并使用 null 调用您的 displayValue 回调。

在允许 多个值 时,此道具没有任何作用,因为选项会在打开和关闭时切换,如果未选择任何内容,则会导致空数组(而不是 null)。

要为下拉菜单的打开/关闭设置动画,您可以使用 Vue 的内置 <transition> 组件。您需要做的就是将您的 ComboboxOptions 实例包装在 <transition> 中,然后会自动应用过渡。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Use Vue's built-in `transition` component to add transitions. -->
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions>
</transition>
</Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果您希望为下拉菜单的不同子级协调多个过渡,请查看 无头 UI 中包含的过渡组件

默认情况下,Combobox 及其子组件都会渲染一个适合该组件的默认元素。

例如,ComboboxLabel 默认情况下会渲染一个 labelComboboxInput 会渲染一个 inputComboboxButton 会渲染一个 buttonComboboxOptions 会渲染一个 ulComboboxOption 会渲染一个 li。相比之下,Combobox 不会渲染元素,而是直接渲染其子元素。

这很容易使用 as 属性更改,该属性存在于每个组件上。

<template> <!-- Render a `div` instead of nothing -->
<Combobox as="div" v-model="selectedPerson">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Render a `div` instead of a `ul` -->
<ComboboxOptions as="div">
<!-- Render a `span` instead of a `li` --> <ComboboxOption
as="span"
v-for="person in filteredPeople" :key="person.id" :value="person" >
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

要告诉一个元素直接渲染其子元素而没有包装元素,请使用 as="template"

<template> <Combobox v-model="selectedPerson"> <!-- Render children directly instead of an `input` --> <ComboboxInput
as="template"
@change="query = $event.target.value" :displayValue="(person) => person.name" >
<input /> </ComboboxInput> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/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 selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

当 Combobox 切换到打开状态时,ComboboxInput 将保持焦点。

ComboboxButton 被默认的 Tab 顺序忽略,这意味着在 ComboboxInput 中按 Tab 将跳过 ComboboxButton

单击 ComboboxButton 会切换选项列表的打开和关闭状态。单击选项列表外部的任何位置都会关闭下拉框。

命令描述

向下箭头, 或者 向上箭头ComboboxInput 处于焦点状态时

打开下拉框并聚焦选定的项目

Enter, 空格, 向下箭头, 或者 向上箭头ComboboxButton 处于焦点状态时

打开下拉框,聚焦输入框并选择选定的项目

Esc 当下拉框打开时

关闭下拉框并恢复输入框中的选定项目

向下箭头 或者 向上箭头当下拉框打开时

聚焦上一个/下一个未禁用的项目

Home 或者 PageUp 当下拉框打开时

聚焦第一个未禁用的项目

End 或者 PageDown 当下拉框打开时

聚焦最后一个未禁用的项目

Enter 当下拉框打开时

选择当前项目

Enter 当下拉框关闭且处于表单中时

提交表单

Tab 当下拉框打开时

选择当前活动的项目并关闭下拉框

A–Z 或者 a–z 当下拉框打开时

调用 onChange,允许你过滤列表

所有相关的 ARIA 属性都由系统自动管理。

主要的 Combobox 组件。

属性默认值描述
astemplate
String | Component

Combobox 应渲染的元素或组件。

v-model
T

选定的值。

defaultValue
T

使用无控制组件时的默认值。

by
keyof T | ((a: T, z: T) => boolean)

使用它来比较对象特定字段,或传递自己的比较函数以完全控制对象的比较方式。

disabledfalse
Boolean

使用它来禁用整个下拉框组件及其相关子元素。

name
String

在表单内使用此组件时使用的名称。

nullable
Boolean

是否可以清除下拉框。

multiplefalse
Boolean

是否可以选择多个选项。

插槽属性描述
value

T

选定的值。

open

Boolean

下拉框是否打开。

disabled

Boolean

下拉框是否禁用。

activeIndex

Number | null

活动选项的索引,如果没有活动选项,则为 null。

activeOption

T | null

活动选项,如果没有活动选项,则为 null。

下拉框的输入框。

属性默认值描述
asinput
String | Component

ComboboxInput 应渲染的元素或组件。

displayValue
(item: T) => string

value 的字符串表示形式。

渲染属性描述
open

Boolean

下拉框是否打开。

disabled

Boolean

下拉框是否禁用。

下拉框的按钮。

属性默认值描述
asbutton
String | Component

ComboboxButton 应渲染的元素或组件。

插槽属性描述
value

T

选定的值。

open

Boolean

下拉框是否打开。

disabled

Boolean

下拉框是否禁用。

一个标签,可用于更好地控制下拉框向屏幕阅读器宣布的文本。它的 id 属性将被自动生成,并通过 aria-labelledby 属性链接到根 Combobox 组件。

属性默认值描述
aslabel
String | Component

ComboboxLabel 应渲染的元素或组件。

插槽属性描述
open

Boolean

下拉框是否打开。

disabled

Boolean

下拉框是否禁用。

直接包裹自定义下拉框中选项列表的组件。

属性默认值描述
asul
String | Component

ComboboxOptions 应渲染的元素或组件。

staticfalse
Boolean

元素是否应忽略内部管理的打开/关闭状态。

unmounttrue
Boolean

元素是否应根据打开/关闭状态卸载或隐藏。

holdfalse
boolean

活动选项是否应在鼠标离开活动选项时保持活动状态。

插槽属性描述
open

Boolean

下拉框是否打开。

用于包裹下拉框中的每个项目。

属性默认值描述
value
T

选项值。

asli
String | Component

ComboboxOption 应渲染的元素或组件。

disabledfalse
Boolean

选项是否应为键盘导航和 ARIA 目的禁用。

插槽属性描述
active

Boolean

选项是否为活动/聚焦选项。

selected

Boolean

选项是否为选定选项。

disabled

Boolean

选项是否为键盘导航和 ARIA 目的禁用。

如果你对使用 Headless UI 和 Tailwind CSS 的预先设计的组件示例感兴趣,请查看 Tailwind UI — 由我们构建的精美设计和精心制作的组件集合。

这是一种支持我们开源项目工作的好方法,使我们能够改进它们并保持良好维护。