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

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

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

npm install @headlessui/react

コンボボックスは、ComboboxCombobox.InputCombobox.ButtonCombobox.OptionsCombobox.Option および Combobox.Label コンポーネントを使用して構築されます。

Combobox.Input は、検索時に自動的に Combobox.Options を開閉します。

あいまい検索ライブラリをクライアント側で使用するか、API へのサーバー側リクエストを行うかなど、結果をどのようにフィルタリングするかは完全にユーザーが管理します。この例では、デモ目的でロジックを単純に保ちます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ 'Durward Reynolds', 'Kenton Towne', 'Therese Wunsch', 'Benedict Kessler', 'Katelyn Rohan', ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person} value={person}> {person} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

Headless UI は、どのリストボックスオプションが現在選択されているか、ポップオーバーが開いているか閉じているか、メニュー内のどの項目がキーボードで現在アクティブであるかなど、各コンポーネントに関する多くの状態を追跡します。

ただし、コンポーネントはヘッドレスで、すぐに使用できる状態では完全にスタイル設定されていないため、各状態に必要なスタイルを自分で提供するまで、UI でこの情報を確認することはできません。

各コンポーネントは、現在の状態に関する情報を、条件付きで異なるスタイルを適用したり、異なるコンテンツをレンダリングするために使用できるレンダープロップを介して公開します。

たとえば、Combobox.Option コンポーネントは、オプションがマウスまたはキーボードで現在フォーカスされているかどうかを示す active 状態と、そのオプションが Combobox の現在の value と一致するかどうかを示す selected 状態を公開します。

import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( /* Use the `active` state to conditionally style the active option. */ /* Use the `selected` state to conditionally style the selected option. */ <Combobox.Option key={person.id} value={person} as={Fragment}>
{({ active, selected }) => (
<li className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
}
`
}
>
{selected && <CheckIcon />}
{person.name} </li> )} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

各コンポーネントの完全なレンダープロップ API については、コンポーネント API ドキュメントを参照してください。

各コンポーネントは、現在の状態に関する情報を、条件付きで異なるスタイルを適用するために使用できる data-headlessui-state 属性を介して公開します。

レンダープロップ APIのいずれかの状態が true の場合、それらはスペースで区切られた文字列としてこの属性にリストされるため、[attr~=value] の形式でCSS属性セレクターを使用してそれらをターゲットにすることができます。

たとえば、コンボボックスが開いていて、2 番目のオプションが selectedactive の両方である場合、子 Combobox.Option コンポーネントを含む Combobox.Options コンポーネントがレンダリングする内容は次のとおりです。

<!-- Rendered `Combobox.Options` --> <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:* などの修飾子でこの属性をターゲットにできます。

import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}
className="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon className="hidden ui-selected:block" />
{person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

文字列のみを値として提供できるネイティブ HTML フォームコントロールとは異なり、Headless UI は複雑なオブジェクトのバインドもサポートしています。

オブジェクトをバインドするときは、選択したオプションの文字列表現が入力にレンダリングされるように、Combobox.InputdisplayValue を設定してください

import { useState } from 'react' import { Combobox } from '@headlessui/react'
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 },
]
function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (
<Combobox value={selectedPerson} onChange={setSelectedPerson}>
<Combobox.Input onChange={(event) => setQuery(event.target.value)}
displayValue={(person) => person.name}
/>
<Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id}
value={person}
disabled={person.unavailable} >
{person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

オブジェクトを値としてバインドする場合は、オブジェクトの同じインスタンスComboboxvalue と対応する Combobox.Option の両方として使用するようにしてください。そうしないと、等しくなることができず、コンボボックスが正しく動作しなくなります。

同じオブジェクトの異なるインスタンスを簡単に操作できるようにするために、by プロパティを使用して、オブジェクトの同一性を比較する代わりに特定のフィールドでオブジェクトを比較できます

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ]
function DepartmentPicker({ selectedDepartment, onChange }) {
const [query, setQuery] = useState('') const filteredDepartments = query === '' ? departments : departments.filter((department) => { return department.name.toLowerCase().includes(query.toLowerCase()) }) return (
<Combobox value={selectedDepartment} by="id" onChange={onChange}>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(department) => department.name} /> <Combobox.Options> {filteredDepartments.map((department) => ( <Combobox.Option key={department.id} value={department}> {department.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

オブジェクトの比較方法を完全に制御したい場合は、独自の比較関数を by プロパティに渡すこともできます

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ]
function compareDepartments(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
function DepartmentPicker({ selectedDepartment, onChange }) { const [query, setQuery] = useState('') const filteredDepartments = query === '' ? departments : departments.filter((department) => { return department.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedDepartment}
by={compareDepartments}
onChange={onChange} >
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(department) => department.name} /> <Combobox.Options> {filteredDepartments.map((department) => ( <Combobox.Option key={department.id} value={department}> {department.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

コンボボックスで複数の値を選択できるようにするには、multiple プロパティを使用し、単一のオプションではなく配列を value に渡します。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() {
const [selectedPeople, setSelectedPeople] = useState([people[0], people[1]])
return ( <Combobox value={selectedPeople} onChange={setSelectedPeople} multiple> {selectedPeople.length > 0 && ( <ul> {selectedPeople.map((person) => ( <li key={person.id}>{person.name}</li> ))} </ul> )}
<Combobox.Input />
<Combobox.Options> {people.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

displayValue プロパティは、selectedPeople が既に入力の上にあるリストに表示されているため、省略されています。Combobox.Input に項目を表示したい場合は、displayValue は配列を受け取ります。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() {
const [selectedPeople, setSelectedPeople] = useState([people[0], people[1]])
return ( <Combobox value={selectedPeople} onChange={setSelectedPeople} multiple> <Combobox.Input
displayValue={(people) =>
people.map((person) => person.name).join(', ')
}
/>
<Combobox.Options> {people.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

これにより、オプションを選択するときにコンボボックスが開いたままになり、オプションを選択するとその場で切り替えられます。

オプションが追加または削除されるたびに、選択したすべてのオプションを含む配列を使用して onChange ハンドラーが呼び出されます。

デフォルトでは、Combobox はスクリーンリーダーのラベルとして入力内容を使用します。支援技術にアナウンスされる内容をより詳細に制御したい場合は、Combobox.Label コンポーネントを使用します。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
<Combobox.Label>Assignee:</Combobox.Label>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

コンボボックスに name プロパティを追加すると、非表示の input 要素がレンダリングされ、選択した値と同期されます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function Example() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <form action="/projects/1/assignee" method="post"> <Combobox value={selectedPerson} onChange={setSelectedPerson}
name="assignee"
>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> <button>Submit</button> </form> ) }

これにより、ネイティブ 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 は状態を内部で追跡するため、非制御コンポーネントとして使用できます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function Example() { const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <form action="/projects/1/assignee" method="post">
<Combobox name="assignee" defaultValue={people[0]}>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> <button>Submit</button> </form> ) }

これにより、コンボボックスをHTMLフォームで使用する場合や、Reactの状態を使用して追跡する代わりにFormDataを使用して状態を収集するフォームAPIを使用する場合にコードを簡略化できます。

コンポーネントの値が変更されたときに副作用を実行する必要がある場合に備えて、提供する onChange プロパティは引き続き呼び出されますが、コンポーネントの状態を自分で追跡するためにそれを使用する必要はありません。

query 値に基づいて動的な Combobox.Option を含めることで、リストに存在しない独自の値をユーザーが入力できるようにすることができます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function Example() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options>
{query.length > 0 && (
<Combobox.Option value={{ id: null, name: query }}>
Create "{query}"
</Combobox.Option>
)}
{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

構築しているものによっては、<Combobox.Options> の外側にあるアクティブなオプションに関する追加情報をレンダリングすると便利な場合があります。たとえば、コマンドパレットのコンテキスト内にあるアクティブなオプションのプレビューなどです。このような状況では、activeOption レンダープロップ引数を読み取って、この情報にアクセスできます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ activeOption }) => (
<> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options>
{activeOption && (
<div>The current active user is: {activeOption.name}</div>
)}
</> )} </Combobox> ) }

activeOption は、現在アクティブな Combobox.Optionvalue になります。

デフォルトでは、Combobox.Options インスタンスは、Combobox コンポーネント自体内で追跡される内部 open 状態に基づいて自動的に表示/非表示になります。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> {/* By default, the `Combobox.Options` will automatically show/hide when typing in the `Combobox.Input`, or when pressing the `Combobox.Button`. */} <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

これを自分で処理したい場合(何らかの理由で追加のラッパー要素を追加する必要がある場合など)、Combobox.Options インスタンスに static プロパティを追加して常にレンダリングするように指示し、Combobox によって提供される open レンダープロップを調べて、表示/非表示にする要素を自分で制御できます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ open }) => (
<> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} />
{open && (
<div> {/* Using `static`, `Combobox.Options` are always rendered and the `open` state is ignored. */}
<Combobox.Options static>
{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </div> )} </> )} </Combobox> ) }

Combobox.Option を無効にするには、disabled プロパティを使用します。これにより、マウスとキーボードで選択できなくなり、上下矢印を押したときにスキップされます。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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 }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( /* Disabled options will be skipped by keyboard navigation. */ <Combobox.Option key={person.id} value={person}
disabled={person.unavailable}
>
<span className={person.unavailable ? 'opacity-75' : ''}> {person.name} </span> </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

デフォルトでは、コンボボックスで値を選択すると、コンボボックスを空の値に戻す方法はありません。入力をクリアしてタブで移動すると、値は以前に選択した値に戻ります。

コンボボックスで空の値をサポートする場合は、nullable プロップを使用してください。

import { useState } from 'react' import { Combobox } from '@headlessui/react' 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 }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (
<Combobox value={selectedPerson} onChange={setSelectedPerson} nullable>
<Combobox.Input onChange={(event) => setQuery(event.target.value)}
displayValue={(person) => person?.name}
/>
<Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

nullable プロップを使用すると、入力をクリアして要素から移動すると、onChange および displayValue コールバックが null で呼び出されます。

このプロップは、複数の値を許可する場合、オプションがオンとオフを切り替えるため、何も選択されていない場合は(nullではなく)空の配列になるため、何も影響しません。

コンボボックスパネルの開閉をアニメーション化するには、提供されている Transition コンポーネントを使用します。Combobox.Options<Transition> でラップするだけで、トランジションが自動的に適用されます。

import { useState } from 'react' import { Combobox, Transition } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} />
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options>
</Transition>
</Combobox> ) }

デフォルトでは、組み込みの Transition コンポーネントは Combobox コンポーネントと自動的に通信して、開閉状態を処理します。ただし、この動作をより詳細に制御する必要がある場合は、明示的に制御できます。

import { useState } from 'react' import { Combobox, Transition } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ open }) => (
<>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> {/* Use the `Transition` + `open` render prop argument to add transitions. */} <Transition
show={open}
enter="transition duration-100 ease-out" enterFrom="transform scale-95 opacity-0" enterTo="transform scale-100 opacity-100" leave="transition duration-75 ease-out" leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" >
{/* Don't forget to add `static` to your `Combobox.Options`! */}
<Combobox.Options static>
{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Transition>
</>
)}
</Combobox> ) }

レンダリングレスであるため、Headless UI コンポーネントは、Framer MotionReact Spring などの React エコシステムの他のアニメーションライブラリともうまく構成できます。

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

たとえば、Combobox.Label はデフォルトで label を、Combobox.Inputinput を、Combobox.Buttonbutton を、Combobox.Optionsul を、Combobox.Optionli をレンダリングします。対照的に、Combobox要素をレンダリングせず、代わりにその子を直接レンダリングします。

as プロップを使用して、コンポーネントを別の要素または独自のカスタムコンポーネントとしてレンダリングします。カスタムコンポーネントが forward refs を使用して、Headless UI が正しく処理できるようにする必要があります。

import { forwardRef, useState } from 'react' import { Combobox } from '@headlessui/react' 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' }, ]
let MyCustomButton = forwardRef(function (props, ref) {
return <button className="..." ref={ref} {...props} />
})
function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (
<Combobox as="div" value={selectedPerson} onChange={setSelectedPerson}>
<Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Button
as={MyCustomButton}
>
Open
</Combobox.Button>
<Combobox.Options as="div">
{filteredPeople.map((person) => ( <Combobox.Option as="span" key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

要素にラッパー要素なしでその子を直接レンダリングするように指示するには、Fragment を使用します。

import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' 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' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> {/* Render a `Fragment` instead of an `input` */} <Combobox.Input
as={Fragment}
onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} >
<input /> </Combobox.Input> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }

コンボボックスが開くと、Combobox.Input はフォーカスされたままになります。

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

Combobox.Button をクリックすると、オプションリストが開閉します。オプションリストの外側をクリックすると、コンボボックスが閉じます。

コマンド説明

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

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

Enter, Space, 下矢印, または 上矢印Combobox.Button がフォーカスされている場合

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

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

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

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

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

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

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

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

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

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

現在の項目を選択します

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

フォームを送信します

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

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

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

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

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

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

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

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

disabledfalse
ブール値

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

value
T

選択された値。

defaultValue
T

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

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

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

onChange
(value: T) => void

新しいオプションが選択されたときに呼び出す関数。

name
文字列

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

nullable
ブール値

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

multiplefalse
ブール値

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

レンダリングプロップ説明
value

T

選択された値。

open

ブール値

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

disabled

ブール値

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

activeIndex

数値 | null

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

activeOption

T | null

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

コンボボックスの入力。

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

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

displayValue
(item: T) => string

value の文字列表現。

レンダリングプロップ説明
open

ブール値

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

disabled

ブール値

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

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

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

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

レンダリングプロップ説明
value

T

選択された値。

open

ブール値

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

disabled

ブール値

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

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

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

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

レンダリングプロップ説明
open

ブール値

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

disabled

ブール値

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

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

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

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

staticfalse
ブール値

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

注意: staticunmount は同時に使用できません。同時に使用しようとすると TypeScript エラーが発生します。

unmounttrue
ブール値

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

注意: staticunmount は同時に使用できません。同時に使用しようとすると TypeScript エラーが発生します。

holdfalse
boolean

マウスがアクティブなオプションから離れても、アクティブなオプションをアクティブな状態に保つかどうか。

レンダリングプロップ説明
open

ブール値

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

Combobox 内の各アイテムをラップするために使用します。

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

オプションの値。

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

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

disabledfalse
ブール値

キーボードナビゲーションおよび ARIA の目的で、オプションを無効にするかどうか。

レンダリングプロップ説明
active

ブール値

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

selected

ブール値

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

disabled

ブール値

キーボードナビゲーションおよび ARIA の目的で、オプションを無効にするかどうか。

Headless UI と Tailwind CSS を使用した、あらかじめデザインされたコンポーネントの例に興味がある場合は、当社が作成した美しくデザインされ、専門的に作られたコンポーネントのコレクションであるTailwind UI をご覧ください。

これは、このようなオープンソースプロジェクトへの取り組みをサポートする素晴らしい方法であり、それらを改善し、適切に保守し続けることを可能にします。