ラジオグループ
ラジオグループは、ネイティブのHTMLラジオ入力と同じ機能を、スタイル付けなしで提供します。カスタムUIのセレクターを構築するのに最適です。
開始するには、npm経由でHeadless UIをインストールします。
**このライブラリはVue 3のみサポート**することにご注意ください。
npm install @headlessui/vue
ラジオグループは、RadioGroup
、RadioGroupLabel
、および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>
オブジェクトを値としてバインドする場合は、RadioGroup
のvalue
と対応する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
の代わりにRadioGroup
にdefaultValue
プロップを提供すると、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
コンポーネントに自動的にリンクされ、カスタムセレクターのセマンティクスとアクセシビリティが向上します。
デフォルトでは、RatioGroupLabel
はlabel
要素をレンダリングし、RadioGroupDescription
は<div>
をレンダリングします。これらは、下記のAPIドキュメントで説明されているように、as
プロップを使用してカスタマイズすることもできます。
また、Label
とDescription
はネストできます。それぞれ、祖先が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 (フォーム内にある場合) | フォームを送信します。 |
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|
v-model | — | T 選択された値。 |
defaultValue | — | T 非制御コンポーネントとして使用する場合のデフォルト値。 |
by | — | keyof T | ((a: T, z: T) => boolean) 特定のフィールドでオブジェクトを比較するためにこれを使用するか、オブジェクトの比較方法を完全に制御するために独自の比較関数を渡します。 |
disabled | false | boolean
|
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|
value | — | T | undefined 現在の |
disabled | false | boolean
|
name | — | 文字列 フォーム内でこのコンポーネントを使用する場合に使用される名前。 |
スロットプロパティ | 説明 |
active |
オプションがアクティブかどうか(マウスまたはキーボードを使用)。 |
チェック済み |
現在のオプションがチェックされているかどうか。 |
disabled |
現在のオプションが無効になっているかどうか。 |
id
属性が自動生成される要素をレンダリングし、その後、aria-labelledby
属性を介して、最も近い祖先であるRadioGroup
またはRadioGroupOption
コンポーネントにリンクされます。
プロパティ | デフォルト値 | 説明 |
as | ラベル | 文字列 | コンポーネント
|
id
属性が自動生成される要素をレンダリングし、その後、aria-describedby
属性を介して、最も近い祖先であるRadioGroup
またはRadioGroupOption
コンポーネントにリンクされます。
プロパティ | デフォルト値 | 説明 |
as | div | 文字列 | コンポーネント
|