コンボボックス (オートコンプリート)
コンボボックスは、キーボードナビゲーションの強力なサポートを備えた、アプリのアクセシブルなオートコンプリートおよびコマンドパレットの基礎です。
まず、npm を使用して Headless UI をインストールします。
このライブラリは Vue 3 のみをサポートしていることに注意してください。
npm install @headlessui/vue
コンボボックスは、Combobox
、ComboboxInput
、ComboboxButton
、ComboboxOptions
、ComboboxOption
、および 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>
オブジェクトを値としてバインドする場合は、Combobox
の value
と対応する 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
は、現在アクティブな ComboboxOption
の value
になります。
デフォルトでは、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
バインディングが更新され、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. -->
<transitionenter-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
をレンダリングし、ComboboxInput
は input
をレンダリングし、ComboboxButton
は button
をレンダリングし、ComboboxOptions
は ul
をレンダリングし、ComboboxOption
は 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` --> <ComboboxOptionas="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
はデフォルトのタブフローでは無視されます。つまり、ComboboxInput
で Tab
を押すと、ComboboxButton
はスキップされます。
ComboboxButton
をクリックすると、オプションリストの開閉が切り替わります。オプションリストの外側をクリックすると、コンボボックスが閉じます。
コマンド | 説明 |
下矢印、または上矢印 | コンボボックスを開き、選択した項目にフォーカスします |
Enter、Space、下矢印、または上矢印 | コンボボックスを開き、入力にフォーカスし、選択した項目を選択します |
Esc コンボボックスが開いている場合 | コンボボックスを閉じ、入力フィールドで選択した項目を復元します |
下矢印または上矢印コンボボックスが開いている場合 | 前/次の無効でない項目にフォーカスします |
HomeまたはPageUp コンボボックスが開いている場合 | 最初の無効でない項目にフォーカスします |
EndまたはPageDown コンボボックスが開いている場合 | 最後の無効でない項目にフォーカスします |
Enter コンボボックスが開いている場合 | 現在の項目を選択します |
Enter コンボボックスが閉じていて、フォーム内にある場合 | フォームを送信します |
Tab コンボボックスが開いている場合 | 現在アクティブな項目を選択し、コンボボックスを閉じます |
A–Zまたはa–z コンボボックスが開いている場合 | リストをフィルタリングできる |
プロパティ | デフォルト | 説明 |
as | template | 文字列 | コンポーネント
|
v-model | — | T 選択された値。 |
defaultValue | — | T 非制御コンポーネントとして使用する場合のデフォルト値。 |
by | — | keyof T | ((a: T, z: T) => boolean) 特定のフィールドでオブジェクトを比較したり、オブジェクトの比較方法を完全に制御するための独自の比較関数を渡したりする場合に使用します。 |
disabled | false | ブール値 コンボボックスコンポーネント全体と関連する子要素を無効にするために使用します。 |
name | — | 文字列 このコンポーネントをフォーム内で使用する場合に使用される名前。 |
nullable | — | ブール値 コンボボックスをクリアできるかどうか。 |
multiple | false | ブール値 複数のオプションを選択できるかどうか。 |
スロットプロパティ | 説明 |
value |
選択された値。 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
activeIndex |
アクティブなオプションのインデックス。アクティブなオプションがない場合は null。 |
activeOption |
アクティブなオプション。アクティブなオプションがない場合は null。 |
プロパティ | デフォルト | 説明 |
as | input | 文字列 | コンポーネント
|
displayValue | — | (item: T) => string
|
レンダープロパティ | 説明 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
プロパティ | デフォルト | 説明 |
as | button | 文字列 | コンポーネント
|
スロットプロパティ | 説明 |
value |
選択された値。 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
コンボボックスがスクリーンリーダーにアナウンスするテキストをより細かく制御するために使用できるラベル。その id
属性は自動的に生成され、aria-labelledby
属性を介してルートの Combobox
コンポーネントにリンクされます。
プロパティ | デフォルト | 説明 |
as | label | 文字列 | コンポーネント
|
スロットプロパティ | 説明 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
プロパティ | デフォルト | 説明 |
as | ul | 文字列 | コンポーネント
|
static | false | ブール値 要素が内部で管理されている開閉状態を無視するかどうか。 |
unmount | true | ブール値 要素を開閉状態に基づいてアンマウントするか非表示にするか。 |
hold | false | ブール値 マウスがアクティブなオプションから離れた場合でも、アクティブなオプションがアクティブな状態を維持する必要があるかどうか。 |
スロットプロパティ | 説明 |
open |
コンボボックスが開いているかどうか。 |
プロパティ | デフォルト | 説明 |
value | — | T オプション値。 |
as | li | 文字列 | コンポーネント
|
disabled | false | ブール値 キーボードナビゲーションと ARIA の目的でオプションを無効にする必要があるかどうか。 |
スロットプロパティ | 説明 |
active |
オプションがアクティブ/フォーカスされたオプションであるかどうか。 |
selected |
オプションが選択されたオプションであるかどうか。 |
disabled |
キーボードナビゲーションと ARIA の目的でオプションが無効になっているかどうか。 |