ラジオグループ
ラジオグループは、ネイティブHTMLラジオ入力と同じ機能を提供しますが、スタイルは一切ありません。セレクター用のカスタムUIを構築するのに最適です。
まず、npm経由でHeadless UIをインストールしてください
npm install @headlessui/react
ラジオグループは、RadioGroup
、Radio
、Field
、およびLabel
コンポーネントを使用して構築されます。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan} className="flex items-center gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400"
>
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" />
</Radio>
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
)
}
Headless UIは、どのラジオグループオプションが現在チェックされているか、ポップオーバーが開いているか閉じているか、またはメニュー内のどの項目が現在キーボードでフォーカスされているかなど、各コンポーネントに関する多くの状態を追跡します。
ただし、コンポーネントはヘッドレスであり、初期状態では完全にスタイルが設定されていないため、各状態に必要なスタイルを自分で提供するまで、UIでこの情報を確認することはできません。
Headless UIコンポーネントのさまざまな状態をスタイル設定する最も簡単な方法は、各コンポーネントが公開するdata-*
属性を使用することです。
たとえば、Radio
コンポーネントは、ラジオが現在チェックされているかどうかを示すdata-checked
属性と、ラジオが現在無効になっているかどうかを示すdata-disabled
属性を公開します。
<!-- Rendered `Radio` -->
<span role="radio" data-checked data-disabled>
<!-- ... -->
</span>
CSS属性セレクターを使用して、これらのデータ属性の存在に基づいて条件付きでスタイルを適用します。Tailwind CSSを使用している場合は、データ属性修飾子を使用すると簡単になります
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan.name} disabled={!plan.available} className="flex items-center gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400 data-[disabled]:bg-gray-100" >
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" /> </Radio>
<Label className="data-[disabled]:opacity-50">{plan.name}</Label> </Field>
))}
</RadioGroup>
)
}
各コンポーネントは、現在の状態に関する情報をレンダープロップを介して公開します。これを使用して、異なるスタイルを条件付きで適用したり、異なるコンテンツをレンダリングしたりできます。
たとえば、Radio
コンポーネントは、ラジオが現在チェックされているかどうかを示すchecked
状態と、ラジオが現在無効になっているかどうかを示すdisabled
状態を公開します。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import clsx from 'clsx'
import { Fragment, useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan.name} disabled={!plan.available} className="flex items-center gap-2">
<Radio as={Fragment} value={plan}> {({ checked, disabled }) => ( <span
className={clsx(
'group flex size-5 items-center justify-center rounded-full border',
checked ? 'bg-blue-400' : 'bg-white', disabled && 'bg-gray-100' )}
>
{checked && <span className="size-2 rounded-full bg-white" />} </span>
)}
</Radio>
<Label as={Fragment}> {({ disabled }) => <label className={disabled && 'opacity-50'}>{plan.name}</label>} </Label> </Field>
))}
</RadioGroup>
)
}
利用可能なすべてのレンダープロップのリストについては、コンポーネントAPIを参照してください。
Field
内でDescription
コンポーネントを使用すると、aria-describedby
属性を使用して自動的にRadio
と関連付けられます
import { Description, Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', description: '12GB, 6 CPUs, 256GB SSD disk' }, { name: 'Business', description: '16GB, 8 CPUs, 512GB SSD disk' }, { name: 'Enterprise', description: '32GB, 12 CPUs, 1TB SSD disk' },]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan} className="flex items-baseline gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400"
>
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" />
</Radio>
<div>
<Label>{plan.name}</Label>
<Description className="opacity-50">{plan.description}</Description> </div>
</Field>
))}
</RadioGroup>
)
}
RadioGroup
にname
プロップを追加すると、非表示のinput
要素がレンダリングされ、ラジオグループの状態と同期されます。
import { Field, Fieldset, Label, Legend, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<form action="/plans" method="post"> <Fieldset>
<Legend>Server size</Legend>
<RadioGroup name="plan" value={selected} onChange={setSelected}> {plans.map((plan) => (
<Field key={plan}>
<Radio value={plan} />
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
</Fieldset>
<button>Submit</button>
</form> )
}
これにより、ネイティブHTMLの<form>
内でラジオグループを使用し、ラジオグループがネイティブHTMLフォームコントロールであるかのように、従来のフォーム送信を行うことができます。
文字列などの基本的な値は、その値を含む単一の非表示の入力としてレンダリングされますが、オブジェクトなどの複雑な値は、名前の角かっこ表記を使用して複数の入力にエンコードされます。
<!-- Rendered hidden input -->
<input type="hidden" name="plan" value="startup" />
value
プロップを省略すると、Headless UIは内部でその状態を追跡するため、アンコントロールコンポーネントとして使用できます。
アンコントロールの場合、defaultValue
プロップを使用してRadioGroup
に初期値を指定します。
import { useState } from 'react'
import { RadioGroup, Radio, Fieldset, Legend, Field, Label } from '@headlessui/react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
return (
<form action="/plans" method="post">
<Fieldset>
<Legend>Server size</Legend>
<RadioGroup name="plan" defaultValue={plans[0]}> {plans.map((plan) => (
<Field key={plan}>
<Radio value={plan} />
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
</Fieldset>
</form>
)
}
これにより、コンボボックスをHTMLフォームで使用する場合や、Reactの状態を使用して追跡する代わりに、FormDataを使用して状態を収集するフォームAPIで使用する場合に、コードを簡略化できます。
コンポーネントの値が変更された場合に副作用を実行する必要がある場合に備えて、提供するonChange
プロップは引き続き呼び出されますが、コンポーネントの状態を自分で追跡するために使用する必要はありません。
文字列のみを値として提供できるネイティブHTMLフォームコントロールとは異なり、Headless UIは複雑なオブジェクトのバインドもサポートしています。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [ { id: 1, name: 'Startup', available: true }, { id: 2, name: 'Business', available: true }, { id: 3, name: 'Enterprise', available: false },]
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} /> <Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
オブジェクトを値としてバインドする場合、オブジェクトの同じインスタンスをRadioGroup
の値と対応するRadio
の両方として使用するようにしてください。そうしないと、それらは等しくならず、ラジオグループが正しく動作しなくなります。
同じオブジェクトの異なるインスタンスを操作しやすくするために、オブジェクトの同一性によって比較する代わりに、特定のフィールドでオブジェクトを比較するためにby
プロップを使用できます。
オブジェクトをvalue
プロップに渡すと、by
は存在する場合にデフォルトでid
になりますが、任意のフィールドに設定できます
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} by="name" onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} />
<Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
オブジェクトの比較方法を完全に制御したい場合は、独自の比較関数をby
プロップに渡すこともできます
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [ { id: 1, name: 'Startup', available: true }, { id: 2, name: 'Business', available: true }, { id: 3, name: 'Enterprise', available: false },]
function comparePlans(a, b) { return a.name.toLowerCase() === b.name.toLowerCase()}
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} by={comparePlans} onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} />
<Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
Radio
コンポーネントにフォーカスがある場合、すべての操作が適用されます。
コマンド | 説明 |
下矢印 または 上矢印 または 左矢印 または 右矢印 |
|
Spaceオプションがまだ選択されていない場合 | フォーカスされているオプションを選択します |
Enterフォームの場合 | フォームを送信します |
プロップ | デフォルト | 説明 |
as | div | 文字列 | コンポーネント 要素またはコンポーネントラジオグループとしてレンダリングする必要があります。 |
value | — | T | undefined
|
defaultValue | — | T 非制御コンポーネントとして使用する場合のデフォルト値。 |
by | — | keyof T | ((a: T, z: T) => boolean) 特定のフィールドでオブジェクトを比較するために使用するか、オブジェクトの比較方法を完全に制御するための独自の比較関数を渡します。 オブジェクトを |
onChange | — | (value: T) => void
|
disabled | false | boolean ラジオグループとそのすべてのラジオを無効にするために使用します。 |
name | — | String フォーム内で使用する場合に使用される名前。ラジオグループフォーム内。 |
form | — | String が属するフォームのID。ラジオグループ
|
データ属性 | レンダープロップ | 説明 |
— | value |
選択された値。 |
プロップ | デフォルト | 説明 |
as | span | 文字列 | コンポーネント 要素またはコンポーネントradioとしてレンダリングする必要があります。 |
value | — | T | undefined この |
disabled | false | Boolean radioが無効かどうか。. |
autoFocus | false | Boolean radio最初にレンダリングされたときにフォーカスを受け取るかどうか。 |
データ属性 | レンダープロップ | 説明 |
data-checked | checked |
radioチェックされている。 |
data-disabled | disabled |
radioが無効になっている。 |
data-focus | focus |
radioフォーカスされている。 |
data-hover | hover |
radioホバーされている。 |
data-autofocus | autofocus |
|
Headless UIを使用したデザイン済みのTailwind CSSラジオグループの例に興味がある場合は、Tailwind UIをチェックしてください。これは、私たちが構築した美しくデザインされ、専門的に作成されたコンポーネントのコレクションです。
これは、このようなオープンソースプロジェクトでの私たちの活動をサポートするのに最適な方法であり、それらを改善し、適切に維持することを可能にします。