ラジオグループ
ラジオグループは、ネイティブのHTMLラジオ入力と同じ機能を提供しますが、スタイルは一切適用されません。カスタムUIのセレクターを構築するのに最適です。
開始するには、npm経由でHeadless UIをインストールします。
npm install @headlessui/react
ラジオグループは、RadioGroup
、RadioGroup.Label
、およびRadioGroup.Option
コンポーネントを使用して構築されます。
オプションをクリックすると選択され、ラジオグループにフォーカスがある場合、矢印キーで選択されたオプションを変更できます。
import { useState } from 'react' import { RadioGroup } from '@headlessui/react' function MyRadioGroup() { let [plan, setPlan] = useState('startup') return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> <RadioGroup.Option value="startup"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Startup</span> )} </RadioGroup.Option> <RadioGroup.Option value="business"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Business</span> )} </RadioGroup.Option> <RadioGroup.Option value="enterprise"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Enterprise</span> )} </RadioGroup.Option> </RadioGroup> ) }
Headless UIは、どのラジオグループオプションが現在チェックされているか、ポップオーバーが開いているか閉じているか、メニューのどのアイテムが現在キーボードでアクティブになっているかなど、各コンポーネントに関する多くの状態を追跡します。
しかし、コンポーネントはヘッドレスで、すぐに使える状態では完全にスタイルが設定されていないため、各状態に対して必要なスタイルを提供するまで、UIでこの情報は表示されません。
各コンポーネントは、レンダープロップスを介して現在の状態に関する情報を公開しており、これを使用して、異なるスタイルを条件付きで適用したり、異なるコンテンツをレンダリングしたりできます。
たとえば、RadioGroup.Option
コンポーネントはactive
状態を公開しており、これはオプションが現在マウスまたはキーボードでフォーカスされているかどうかを示し、checked
状態は、そのオプションがRadioGroup
の現在のvalue
と一致するかどうかを示します。
import { useState, Fragment } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const plans = ['Statup', 'Business', 'Enterprise'] function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( /* Use the `active` state to conditionally style the active option. */ /* Use the `checked` state to conditionally style the checked option. */ <RadioGroup.Option key={plan} value={plan} as={Fragment}>
{({ active, checked }) => (<li className={`${active ? 'bg-blue-500 text-white' : 'bg-white text-black'}`} >{checked && <CheckIcon />}{plan} </li> )} </RadioGroup.Option> ))} </RadioGroup> ) }
各コンポーネントの完全なレンダープロップAPIについては、コンポーネントAPIドキュメントを参照してください。
各コンポーネントは、条件付きで異なるスタイルを適用するために使用できるdata-headlessui-state
属性を介して、現在の状態に関する情報を公開します。
レンダープロップAPIのいずれかの状態がtrue
の場合、それらはスペース区切りの文字列としてこの属性にリストされます。これにより、[attr~=value]
形式のCSS属性セレクターを使用してターゲットを指定できます。
たとえば、ラジオグループが開いていて、2番目のオプションがchecked
とactive
の両方である場合、RadioGroup
コンポーネントと一部の子RadioGroup.Option
コンポーネントは次のようにレンダリングされます。
<!-- Rendered `RadioGroup` --> <div role="radiogroup"> <li data-headlessui-state="">Statup</li> <li data-headlessui-state="active checked">Business</li> <li data-headlessui-state="">Enterprise</li> </div>
Tailwind CSSを使用している場合は、@headlessui/tailwindcssプラグインを使用して、ui-open:*
やui-active:*
などの修飾子でこの属性をターゲットにすることができます。
import { useState, Fragment } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const plans = ['Statup', 'Business', 'Enterprise'] function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan} value={plan}
className="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"><CheckIcon className="hidden ui-checked:block" />{plan} </RadioGroup.Option> ))} </RadioGroup> ) }
値として文字列しか提供できないネイティブのHTMLフォームコントロールとは異なり、Headless UIは複雑なオブジェクトのバインドもサポートしています。
import { useState } from 'react' import { RadioGroup } from '@headlessui/react'
const plans = [{ id: 1, name: 'Startup' },{ id: 2, name: 'Business' },{ id: 3, name: 'Enterprise' },]function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return (<RadioGroup value={plan} onChange={setPlan}><RadioGroup.Label>Plan:</RadioGroup.Label> {plans.map((plan) => (<RadioGroup.Option key={plan.id} value={plan}>{plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }
オブジェクトを値としてバインドする場合は、RadioGroup
のvalue
と対応するRadioGroup.Option
の両方で、オブジェクトの同じインスタンスを使用する必要があります。そうでない場合、等しくなくなり、ラジオグループが正しく動作しなくなります。
同じオブジェクトの異なるインスタンスをより簡単に操作するために、by
プロップを使用して、オブジェクトの同一性を比較する代わりに、特定のフィールドでオブジェクトを比較できます。
import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
function PlanPicker({ checkedPlan, onChange }) {return (<RadioGroup value={checkedPlan} by="id" onChange={onChange}><RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }
オブジェクトの比較方法を完全に制御したい場合は、独自の比較関数をby
プロップに渡すこともできます。
import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
function comparePlans(a, b) {return a.name.toLowerCase() === b.name.toLowerCase()}function PlanPicker({ checkedPlan, onChange }) { return (<RadioGroup value={checkedPlan} by={comparePlans} onChange={onChange}><RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }
リストボックスにname
プロップを追加すると、非表示のinput
要素がレンダリングされ、選択した値と同期が維持されます。
import { useState } from 'react' import { RadioGroup } from '@headlessui/react' const plans = ['startup', 'business', 'enterprise'] function Example() { const [plan, setPlan] = useState(plans[0]) return ( <form action="/billing" method="post">
<RadioGroup value={plan} onChange={setPlan} name="plan"><RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan} value={plan}> {plan} </RadioGroup.Option> ))} </RadioGroup> <button>Submit</button> </form> ) }
これにより、ネイティブHTML<form>
内にラジオグループを使用し、ラジオグループがネイティブHTMLフォームコントロールであるかのように、従来のフォーム送信を行うことができます。
文字列などの基本的な値は、その値を含む単一の非表示入力としてレンダリングされますが、オブジェクトなどの複雑な値は、名前に対して角括弧表記を使用して複数の入力にエンコードされます。
<input type="hidden" name="plan" value="startup" />
value
の代わりにRadioGroup
にdefaultValue
プロップを提供すると、Headless UIは内部的にその状態を追跡し、非制御コンポーネントとして使用できます。
import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ] function Example() { return ( <form action="/companies" method="post">
<RadioGroup name="plan" defaultValue={plans[0]}><RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> <button>Submit</button> </form> ) }
これは、コンボボックスをHTMLフォームまたはFormDataを使用して状態を収集するフォームAPIで使用する場合、Reactの状態を使用して追跡する必要がないため、コードを簡素化できます。
コンポーネントの値が変更された場合、提供したonChange
プロップは依然として呼び出されます(副作用を実行する必要がある場合)。ただし、コンポーネントの状態を自分で追跡するために使用する必要はありません。
RadioGroup.Label
およびRadioGroup.Description
コンポーネントを使用して、各オプションのコンテンツをマークアップできます。これにより、aria-labelledby
およびaria-describedby
属性と自動生成されたid
を介して、各コンポーネントが祖先のRadioGroup.Option
コンポーネントに自動的にリンクされ、カスタムセレクターのセマンティクスとアクセシビリティが向上します。
デフォルトでは、RatioGroup.Label
はlabel
要素をレンダリングし、RadioGroup.Description
は<div>
をレンダリングします。これらは、下記のAPIドキュメントで説明されているように、as
プロップを使用してカスタマイズすることもできます。
また、Label
とDescription
はネストできます。それぞれが、祖先がRadioGroup.Option
かルートRadioGroup
のどちらであるかに関係なく、最も近い祖先コンポーネントを参照します。
import { useState } from 'react' import { RadioGroup } from '@headlessui/react' function MyRadioGroup() { const [selected, setSelected] = useState('startup') return ( <RadioGroup value={selected} onChange={setSelected}> {/* This label is for the root `RadioGroup`. */}
<RadioGroup.Label className="sr-only">Plan</RadioGroup.Label><div className="rounded-md bg-white"> <RadioGroup.Option value="startup" className={({ checked }) => ` ${checked ? 'border-indigo-200 bg-indigo-50' : 'border-gray-200'} relative flex border p-4 `} > {({ checked }) => ( <div className="flex flex-col"> {/* This label is for the `RadioGroup.Option`. */}<RadioGroup.Labelas="span"className={`${checked ? 'text-indigo-900' : 'text-gray-900'} block text-sm font-medium`}>Startup</RadioGroup.Label>{/* This description is for the `RadioGroup.Option`. */}<RadioGroup.Descriptionas="span"className={`${checked ? 'text-indigo-700' : 'text-gray-500'} block text-sm`}>Up to 5 active job postings</RadioGroup.Description></div> )} </RadioGroup.Option> </div> </RadioGroup> ) }
RadioGroup.Option
をクリックすると選択されます。
すべての操作は、RadioGroup
コンポーネントにフォーカスがある場合に適用されます。
コマンド | 説明 |
下矢印または上矢印または左矢印または右矢印 | ラジオグループのオプションを巡回します。 |
Space (オプションがまだ選択されていない場合) | 最初のオプションを選択します。 |
Enter (フォーム内にある場合) | フォームを送信します。 |
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|
value | — | T | undefined
|
defaultValue | — | T 非制御コンポーネントとして使用する場合のデフォルト値。 |
by | — | keyof T | ((a: T, z: T) => boolean) 特定のフィールドでオブジェクトを比較するためにこれを使用するか、オブジェクトの比較方法を完全に制御するために独自の比較関数を渡します。 |
onChange | — | () => void
|
disabled | false | boolean
|
name | — | 文字列 このコンポーネントをフォーム内で使用する場合に使用される名前。 |
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|
value | — | T | undefined 現在の |
disabled | false | boolean
|
レンダープロップ | 説明 |
active |
オプションがアクティブかどうか(マウスまたはキーボードを使用)。 |
checked |
現在のオプションがチェックされた値かどうか。 |
disabled |
現在のオプションが無効かどうか。 |
id
属性が自動的に生成される要素をレンダリングし、その後、aria-labelledby
属性を介して、最も近い祖先RadioGroup
またはRadioGroup.Option
コンポーネントにリンクされます。
プロパティ | デフォルト値 | 説明 |
as | label | 文字列 | コンポーネント
|
id
属性が自動的に生成される要素をレンダリングし、その後、aria-describedby
属性を介して、最も近い祖先RadioGroup
またはRadioGroup.Option
コンポーネントにリンクされます。
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|