無線電組

無線電組提供與原生 HTML 無線電輸入相同的機制,但不會夾帶任何樣式。這些組非常適合為選取器打造自訂 UI。

首先,透過 npm 安裝 Headless UI。

請注意這個函式庫僅支援 Vue 3

npm install @headlessui/vue

無線電組使用 RadioGroupRadioGroupLabelRadioGroupOption 元件建立。

按一下選項會選取它,無線電組獲得焦點時,箭頭鍵會變更選取的選項。

<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` 屬性公開目前狀態的資訊,你可以透過此屬性條件式地套用不同的樣式。

當 `slot prop API`的任何狀態為 `true` 時,它們會以空白分隔的字串列在此屬性中,因此你可以使用形式為 `[attr~=value]` 的 CSS 屬性選擇器來鎖定這些狀態。

例如,以下是在 `RadioGroup` 組件中,當 radiogroup 開啟且第二個項目為 `active` 狀態時所渲染的內容

<!-- 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` 和相對應 `RadioGroupOption` 的 `value` 的物件之相同實例,否則它們無法相等,且會導致 radiogroup 行為不正確。

為了更容易使用相同物件的不同實例,你可以使用 `by` prop 來比較物件的特定欄位,而不是比較物件身分

<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` prop

<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` prop 新增到 listbox 中,隱藏的 `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 中使用一個 radio group <form>,並進行傳統表單提交,就好像你的 radio group 是原生 HTML 表單控制元件一樣。

像字串之類的基本值將會被渲染成包含該值的單一隱藏輸入,但像物件之類的複雜值則會使用方括號表示法編碼成多個輸入,作為名稱。

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

如果您提供一個 defaultValueRadioGroup 而不是 value,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 表單搭配使用 使用表單 API 透過 FormData 收集狀態而不是使用 React 狀態追蹤時,這可以簡化您的程式碼。

如果您需要執行任何副作用,當元件的值改變時,您提供的任何 @update:modelValue 仍會被呼叫,但是您不需要自行使用它來追蹤元件的狀態。

您可以使用 RadioGroupLabelRadioGroupDescription 元件標記每個選項的內容。這樣做將自動透過 aria-labelledbyaria-describedby 屬性以及自動產生的 id 將每個元件連結到它的祖先 RadioGroupOption 元件,進而改善您自訂選擇器的語意和無障礙性。

預設情況下,RatioGroupLabel 會呈現一個 label 元素,而 RadioGroupDescription 會呈現一個 <div>。這些也可以使用 as 屬性自訂,如下面的 API 文件所述。

另外要注意的是,標籤說明可以巢狀。每個都會參考它最近的祖先元件,無論該祖先元件是 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 元件取得焦點時,所有的互動都會套用。

命令說明

向下箭頭向上箭頭向左箭頭向右箭頭

巡迴 RadioGroup 的選項

空白鍵,當仍未選取任何選項

選取第一個選項

輸入鍵,在表單中

送出表單

所有相關 ARIA 屬性都會自動管理。

主要的 Radio Group 元件。

屬性預設值說明
asdiv
字串 | 元件

RadioGroup 應呈現為的元素或元件。

v-model
T

選取的值。

defaultValue
T

當將其用作非受控元件時使用的預設值。

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

使用此方法可以透過某特定欄位來比較物件,也可以傳遞您自訂的比較函式,以便完全控制如何比較物件。

disabledfalse
布林值

RadioGroup 及其全部的 RadioGroupOption 是否停用。

每個可選取選項的外層元件。

屬性預設值說明
asdiv
字串 | 元件

RadioGroupOption 應呈現的元素或元件。

value
T | 未定義

目前 RadioGroupOption 的值。類型應與 RadioGroup 元件中 value 的類型相符。

disabledfalse
布林值

RadioGroupOption 是否已停用。

name
字串

使用這個元件所在的表單時所使用的名稱。

插槽 Prop說明
active

布林值

是否顯示選項為啟用狀態 (使用滑鼠或鍵盤)。

checked

布林值

目前選項是否為已選取值。

disabled

布林值

目前選項是否已停用。

呈現一個 id 屬性會自動產生的元素,並透過 aria-labelledby 屬性連結到其最近的祖先 RadioGroupRadioGroupOption 元件。

屬性預設值說明
aslabel
字串 | 元件

RadioGroupLabel 應呈現的元素或元件。

呈現一個 id 屬性會自動產生的元素,並透過 aria-describedby 屬性連結到其最近的祖先 RadioGroupRadioGroupOption 元件。

屬性預設值說明
asdiv
字串 | 元件

RadioGroupDescription 應呈現的元素或元件。

如果你有興趣使用無介面 UI 和 Tailwind CSS 的預先設計元件範例,請查看 Tailwind UI,這是我們所建立一系列設計精良且製作精良的元件。

這是支持我們進行像這樣的開源專案的好方法,並讓我們能持續改進並妥善維護它們。