ラジオグループ

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

まず、npm経由でHeadless UIをインストールしてください

npm install @headlessui/react

ラジオグループは、RadioGroupRadioField、および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> ) }

RadioGroupnameプロップを追加すると、非表示の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コンポーネントにフォーカスがある場合、すべての操作が適用されます。

コマンド説明

下矢印 または 上矢印 または 左矢印 または 右矢印

RadioGroupのオプションを順番に切り替えます

Spaceオプションがまだ選択されていない場合

フォーカスされているオプションを選択します

Enterフォームの場合

フォームを送信します

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

プロップデフォルト説明
asdiv
文字列 | コンポーネント

要素またはコンポーネントラジオグループとしてレンダリングする必要があります。

value
T | undefined

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

defaultValue
T

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

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

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

オブジェクトをvalueプロパティに渡すと、byidが存在する場合はデフォルトでidになります。

onChange
(value: T) => void

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

disabledfalse
boolean

ラジオグループとそのすべてのラジオを無効にするために使用します。

name
String

フォーム内で使用する場合に使用される名前。ラジオグループフォーム内。

form
String

が属するフォームのID。ラジオグループ

nameが提供されているがformが提供されていない場合、ラジオグループは、最も近い祖先のform要素にその状態を追加します。

データ属性レンダープロップ説明
value

T

選択された値。

選択可能な各オプションのコンポーネント。

プロップデフォルト説明
asspan
文字列 | コンポーネント

要素またはコンポーネントradioとしてレンダリングする必要があります。

value
T | undefined

このRadioの値。型は、RadioGroupコンポーネントのvalueの型と一致する必要があります。

disabledfalse
Boolean

radioが無効かどうか。.

autoFocusfalse
Boolean

radio最初にレンダリングされたときにフォーカスを受け取るかどうか。

データ属性レンダープロップ説明
data-checkedchecked

Boolean

radioチェックされている。

data-disableddisabled

Boolean

radioが無効になっている。

data-focusfocus

Boolean

radioフォーカスされている。

data-hoverhover

Boolean

radioホバーされている。

data-autofocusautofocus

Boolean

autoFocusプロパティがtrueに設定されたかどうか。

Headless UIを使用したデザイン済みのTailwind CSSラジオグループの例に興味がある場合はTailwind UIをチェックしてください。これは、私たちが構築した美しくデザインされ、専門的に作成されたコンポーネントのコレクションです。

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