ラジオグループ

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

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

**このライブラリはVue 3のみサポート**することにご注意ください。

npm install @headlessui/vue

ラジオグループは、RadioGroupRadioGroupLabel、およびRadioGroupOptionコンポーネントを使用して構築されています。

オプションをクリックすると選択され、ラジオグループにフォーカスがある場合、矢印キーで選択されたオプションを変更できます。

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-slot="{ checked }" value="startup"> <span :class="checked ? 'bg-blue-200' : ''">Startup</span> </RadioGroupOption> <RadioGroupOption v-slot="{ checked }" value="business"> <span :class="checked ? 'bg-blue-200' : ''">Business</span> </RadioGroupOption> <RadioGroupOption v-slot="{ checked }" value="enterprise"> <span :class="checked ? 'bg-blue-200' : ''">Enterprise</span> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plan = ref('startup') </script>

Headless UIは、どのラジオグループオプションが現在選択されているか、ポップオーバーが開いているか閉じているか、キーボードで現在アクティブなラジオグループのアイテムなど、各コンポーネントに関する多くの状態を追跡します。

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

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

たとえば、RadioGroupOptionコンポーネントはactive状態を公開しており、マウスまたはキーボードを介してアイテムが現在フォーカスされているかどうかを示します。

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <!-- Use the `active` state to conditionally style the active option. --> <!-- Use the `checked` state to conditionally style the checked option. --> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan" as="template"
v-slot="{ active, checked }"
>
<li :class="{
'bg-blue-500 text-white': active,
'bg-white text-black': !active,
}"
>
<CheckIcon v-show="checked" />
{{ plan }} </li> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const plans = ['Startup', 'Business', 'Enterprise'] const plan = ref(plans[0]) </script>

使用可能なすべてのスロットプロップの完全なリストについては、コンポーネントAPIドキュメントを参照してください。

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

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

たとえば、ラジオグループが開いていて、2番目のアイテムがactiveである場合、いくつかの子RadioGroupOptionコンポーネントを含むRadioGroupコンポーネントは次のようにレンダリングされます。

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

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan" as="template" > <li
class="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon class="hidden ui-checked:block" />
{{ plan }} </li> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const plans = ['Startup', 'Business', 'Enterprise'] const plan = ref(plans[0]) </script>

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

<template>
<RadioGroup v-model="plan">
<RadioGroupLabel>Plan</RadioGroupLabel>
<RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan">
{{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue'
const plans = [
{ id: 1, name: 'Startup' },
{ id: 2, name: 'Business' },
{ id: 3, name: 'Enterprise' },
]
const plan = ref(plans[1])
</script>

オブジェクトを値としてバインドする場合は、RadioGroupvalueと対応するRadioGroupOptionの両方でオブジェクトの同じインスタンスを使用することが重要です。そうでない場合、等しくならず、ラジオグループが正しく動作しなくなります。

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

<template> <RadioGroup :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
by="id"
>
<RadioGroupLabel>Assignee</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan"> {{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue']) const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ] </script>

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

<template> <RadioGroup :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
:by="comparePlans"
>
<RadioGroupLabel>Assignee</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan"> {{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue'])
function comparePlans(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
</script>

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

<template> <form action="/billing" method="post">
<RadioGroup v-model="plan" name="plan">
<RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan"> {{ plan }} </RadioGroupOption> </RadioGroup> <button>Submit</button> </form> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plans = ['startup', 'business', 'enterprise'] const plan = ref(plans[0]) </script>

これにより、ネイティブHTML<form>内にラジオグループを使用し、ラジオグループがネイティブHTMLフォームコントロールであるかのように、従来のフォーム送信を行うことができます。

文字列などの基本的な値は、その値を含む単一の非表示入力としてレンダリングされますが、オブジェクトなどの複雑な値は、名前に対して角括弧表記を使用して複数の入力にエンコードされます。

<input type="hidden" name="plan" value="startup" />

valueの代わりにRadioGroupdefaultValueプロップを提供すると、Headless UIが内部的に状態を追跡し、非制御コンポーネントとして使用できます。

<template> <form action="/billing" method="post">
<RadioGroup name="plan" :defaultValue="plans[0]">
<RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan"> {{ plan }} </RadioGroupOption> </RadioGroup> <button>Submit</button> </form> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plans = ['startup', 'business', 'enterprise'] </script>

これは、コンボボックスをHTMLフォームまたはReactの状態を使用して追跡するのではなく、FormDataを使用して状態を収集するフォームAPIで使用する場合に、コードを簡素化できます。

コンポーネントの値が変更された場合、提供された@update:modelValueプロップは引き続き呼び出されます(副作用を実行する必要がある場合)。ただし、コンポーネントの状態を自分で追跡する必要はありません。

RadioGroupLabelおよびRadioGroupDescriptionコンポーネントを使用して、各オプションのコンテンツをマークアップできます。これにより、各コンポーネントはaria-labelledbyおよびaria-describedby属性と自動生成されたidを介して祖先のRadioGroupOptionコンポーネントに自動的にリンクされ、カスタムセレクターのセマンティクスとアクセシビリティが向上します。

デフォルトでは、RatioGroupLabellabel要素をレンダリングし、RadioGroupDescription<div>をレンダリングします。これらは、下記のAPIドキュメントで説明されているように、asプロップを使用してカスタマイズすることもできます。

また、LabelDescriptionはネストできます。それぞれ、祖先がRadioGroupOptionかルートRadioGroupのどちらであるかにかかわらず、最も近い祖先コンポーネントを参照します。

<template> <RadioGroup v-model="plan"> <!-- This label is for the root `RadioGroup` -->
<RadioGroupLabel class="sr-only">Plan</RadioGroupLabel>
<div class="rounded-md bg-white"> <RadioGroupOption value="startup" as="template" v-slot="{ checked }"> <div :class='checked ? "bg-indigo-50 border-indigo-200" : "border-gray-200"' class="relative flex border p-4" > <div class="flex flex-col"> <!-- This label is for the `RadioGroupOption` -->
<RadioGroupLabel as="template">
<span
:class='checked ? "text-indigo-900" : "text-gray-900"'
class="block text-sm font-medium"
>Startup</span
>
</RadioGroupLabel>
<!-- This description is for the `RadioGroupOption` -->
<RadioGroupDescription as="template">
<span
:class='checked ? "text-indigo-700" : "text-gray-500"'
class="block text-sm"
>Up to 5 active job postings</span
>
</RadioGroupDescription>
</div> </div> </RadioGroupOption> </div> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, RadioGroupDescription, } from '@headlessui/vue' const plan = ref('startup') </script>

RadioGroupOptionをクリックすると選択されます。

RadioGroupコンポーネントにフォーカスがある場合、すべての操作が適用されます。

コマンド説明

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

ラジオグループのオプションを循環します。

スペース (まだオプションが選択されていない場合)

最初のオプションを選択します。

Enter (フォーム内にある場合)

フォームを送信します。

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

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

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

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

v-model
T

選択された値。

defaultValue
T

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

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

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

disabledfalse
boolean

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

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

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

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

value
T | undefined

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

disabledfalse
boolean

RadioGroupOptionが無効になっているかどうか。

name
文字列

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

スロットプロパティ説明
active

Boolean

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

チェック済み

Boolean

現在のオプションがチェックされているかどうか。

disabled

boolean

現在のオプションが無効になっているかどうか。

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

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

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

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

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

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

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

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