ラジオグループ

ラジオグループは、ネイティブのHTMLラジオ入力と同じ機能を提供しますが、スタイルは一切適用されません。カスタムUIのセレクターを構築するのに最適です。

開始するには、npm経由でHeadless UIをインストールします。

npm install @headlessui/react

ラジオグループは、RadioGroupRadioGroup.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番目のオプションがcheckedactiveの両方である場合、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> ) }

オブジェクトを値としてバインドする場合は、RadioGroupvalueと対応する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の代わりにRadioGroupdefaultValueプロップを提供すると、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.Labellabel要素をレンダリングし、RadioGroup.Description<div>をレンダリングします。これらは、下記のAPIドキュメントで説明されているように、asプロップを使用してカスタマイズすることもできます。

また、LabelDescriptionはネストできます。それぞれが、祖先が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.Label
as="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.Description
as="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 (フォーム内にある場合)

フォームを送信します。

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

メインのラジオグループコンポーネント。

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

RadioGroupがレンダリングされる要素またはコンポーネント。

value
T | undefined

RadioGroupで現在選択されている値。

defaultValue
T

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

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

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

onChange
() => void

RadioGroupの値を更新するために呼び出される関数。

disabledfalse
boolean

RadioGroupとそのすべてのRadioGroup.Optionが無効になっているかどうか。

name
文字列

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

各選択可能なオプションのラッパーコンポーネントです。

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

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

value
T | undefined

現在のRadioGroup.Optionの値。その型は、RadioGroupコンポーネントのvalueの型と一致する必要があります。

disabledfalse
boolean

RadioGroup.Optionが無効かどうか。

レンダープロップ説明
active

ブール値

オプションがアクティブかどうか(マウスまたはキーボードを使用)。

checked

ブール値

現在のオプションがチェックされた値かどうか。

disabled

boolean

現在のオプションが無効かどうか。

id属性が自動的に生成される要素をレンダリングし、その後、aria-labelledby属性を介して、最も近い祖先RadioGroupまたはRadioGroup.Optionコンポーネントにリンクされます。

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

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

id属性が自動的に生成される要素をレンダリングし、その後、aria-describedby属性を介して、最も近い祖先RadioGroupまたはRadioGroup.Optionコンポーネントにリンクされます。

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

RadioGroup.Descriptionがレンダリングされる要素またはコンポーネント。

Headless UIとTailwind CSSを使用した、事前にデザインされたコンポーネント例にご興味のある方は、私たちが作成した美しくデザインされ、熟練した職人が作成したコンポーネントのコレクションであるTailwind UIをご覧ください。

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