コンボボックス (オートコンプリート)

コンボボックスは、キーボードナビゲーションの強力なサポートを備えた、アプリのアクセシブルなオートコンプリートおよびコマンドパレットの基礎です。

まず、npm を使用して Headless UI をインストールします。

このライブラリは Vue 3 のみをサポートしていることに注意してください

npm install @headlessui/vue

コンボボックスは、ComboboxComboboxInputComboboxButtonComboboxOptionsComboboxOption、および ComboboxLabel コンポーネントを使用して構築されます。

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>

Headless 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 の場合、それらはこの属性にスペースで区切られた文字列としてリストされるため、[attr~=value] の形式でCSS属性セレクターを使用してターゲットにできます。

たとえば、コンボボックスが開いていて、2番目の項目が active の場合、子ComboboxOptionコンポーネントを持つ ComboboxOptions コンポーネントがレンダリングされる内容は次のとおりです。

<!-- 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フォームコントロールとは異なり、Headless 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" />

value の代わりに defaultValue プロップを Combobox に提供すると、Headless 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フォームで使用する場合、またはReactの状態を使用して追跡するのではなく、FormDataを使用して状態を収集するフォーム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>

(たとえば、何らかの理由で追加のラッパー要素を追加する必要があるなどの理由で) これを自分で処理したい場合は、ComboboxOptions インスタンスに static プロップを追加して、常にレンダリングするように指示し、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 バインディングが更新され、nulldisplayValue コールバックが呼び出されます。

このプロップは、オプションのオンとオフが切り替わるため、何も選択されていない場合は (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>

コンボボックスの異なる子要素に対して複数のトランジションを調整したい場合は、Headless UI に含まれているトランジションコンポーネントを確認してください。

デフォルトでは、Combobox とそのサブコンポーネントはそれぞれ、そのコンポーネントに適したデフォルトの要素をレンダリングします。

例えば、ComboboxLabel はデフォルトで label をレンダリングし、ComboboxInputinput をレンダリングし、ComboboxButtonbutton をレンダリングし、ComboboxOptionsul をレンダリングし、ComboboxOptionli をレンダリングします。対照的に、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>

コンボボックスが開かれると、ComboboxInput はフォーカスされたままになります。

ComboboxButton はデフォルトのタブフローでは無視されます。つまり、ComboboxInputTab を押すと、ComboboxButton はスキップされます。

ComboboxButton をクリックすると、オプションリストの開閉が切り替わります。オプションリストの外側をクリックすると、コンボボックスが閉じます。

コマンド説明

下矢印または上矢印ComboboxInput がフォーカスされている場合

コンボボックスを開き、選択した項目にフォーカスします

EnterSpace下矢印または上矢印ComboboxButton がフォーカスされている場合

コンボボックスを開き、入力にフォーカスし、選択した項目を選択します

Esc コンボボックスが開いている場合

コンボボックスを閉じ、入力フィールドで選択した項目を復元します

下矢印または上矢印コンボボックスが開いている場合

前/次の無効でない項目にフォーカスします

HomeまたはPageUp コンボボックスが開いている場合

最初の無効でない項目にフォーカスします

EndまたはPageDown コンボボックスが開いている場合

最後の無効でない項目にフォーカスします

Enter コンボボックスが開いている場合

現在の項目を選択します

Enter コンボボックスが閉じていて、フォーム内にある場合

フォームを送信します

Tab コンボボックスが開いている場合

現在アクティブな項目を選択し、コンボボックスを閉じます

A–Zまたはa–z コンボボックスが開いている場合

リストをフィルタリングできる onChange を呼び出します

関連するすべての ARIA 属性は自動的に管理されます。

メインのコンボボックスコンポーネント。

プロパティデフォルト説明
astemplate
文字列 | コンポーネント

Combobox がレンダリングする要素またはコンポーネント。

v-model
T

選択された値。

defaultValue
T

非制御コンポーネントとして使用する場合のデフォルト値。

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

特定のフィールドでオブジェクトを比較したり、オブジェクトの比較方法を完全に制御するための独自の比較関数を渡したりする場合に使用します。

disabledfalse
ブール値

コンボボックスコンポーネント全体と関連する子要素を無効にするために使用します。

name
文字列

このコンポーネントをフォーム内で使用する場合に使用される名前。

nullable
ブール値

コンボボックスをクリアできるかどうか。

multiplefalse
ブール値

複数のオプションを選択できるかどうか。

スロットプロパティ説明
value

T

選択された値。

open

ブール値

コンボボックスが開いているかどうか。

disabled

ブール値

コンボボックスが無効になっているかどうか。

activeIndex

数値 | null

アクティブなオプションのインデックス。アクティブなオプションがない場合は null。

activeOption

T | null

アクティブなオプション。アクティブなオプションがない場合は null。

コンボボックスの入力。

プロパティデフォルト説明
asinput
文字列 | コンポーネント

ComboboxInput がレンダリングする要素またはコンポーネント。

displayValue
(item: T) => string

value の文字列表現。

レンダープロパティ説明
open

ブール値

コンボボックスが開いているかどうか。

disabled

ブール値

コンボボックスが無効になっているかどうか。

コンボボックスのボタン。

プロパティデフォルト説明
asbutton
文字列 | コンポーネント

ComboboxButton がレンダリングする要素またはコンポーネント。

スロットプロパティ説明
value

T

選択された値。

open

ブール値

コンボボックスが開いているかどうか。

disabled

ブール値

コンボボックスが無効になっているかどうか。

コンボボックスがスクリーンリーダーにアナウンスするテキストをより細かく制御するために使用できるラベル。その id 属性は自動的に生成され、aria-labelledby 属性を介してルートの Combobox コンポーネントにリンクされます。

プロパティデフォルト説明
aslabel
文字列 | コンポーネント

ComboboxLabel がレンダリングする要素またはコンポーネント。

スロットプロパティ説明
open

ブール値

コンボボックスが開いているかどうか。

disabled

ブール値

コンボボックスが無効になっているかどうか。

カスタムコンボボックスのオプションリストを直接ラップするコンポーネント。

プロパティデフォルト説明
asul
文字列 | コンポーネント

ComboboxOptions がレンダリングする要素またはコンポーネント。

staticfalse
ブール値

要素が内部で管理されている開閉状態を無視するかどうか。

unmounttrue
ブール値

要素を開閉状態に基づいてアンマウントするか非表示にするか。

holdfalse
ブール値

マウスがアクティブなオプションから離れた場合でも、アクティブなオプションがアクティブな状態を維持する必要があるかどうか。

スロットプロパティ説明
open

ブール値

コンボボックスが開いているかどうか。

コンボボックス内の各項目をラップするために使用します。

プロパティデフォルト説明
value
T

オプション値。

asli
文字列 | コンポーネント

ComboboxOption がレンダリングする要素またはコンポーネント。

disabledfalse
ブール値

キーボードナビゲーションと ARIA の目的でオプションを無効にする必要があるかどうか。

スロットプロパティ説明
active

ブール値

オプションがアクティブ/フォーカスされたオプションであるかどうか。

selected

ブール値

オプションが選択されたオプションであるかどうか。

disabled

ブール値

キーボードナビゲーションと ARIA の目的でオプションが無効になっているかどうか。

Headless UI と Tailwind CSS を使用したデザイン済みコンポーネントの例にご興味がある場合は、弊社が作成した美しくデザインされ、専門的に作成されたコンポーネントのコレクションである **Tailwind UI** を確認してください。

これは、このようなオープンソースプロジェクトでの私たちの活動をサポートする素晴らしい方法であり、それらを改善し、適切にメンテナンスし続けることを可能にします。