無線電組
無線電組提供與原生 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` 屬性公開目前狀態的資訊,你可以透過此屬性條件式地套用不同的樣式。
當 `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" />
如果您提供一個 defaultValue
給 RadioGroup
而不是 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
仍會被呼叫,但是您不需要自行使用它來追蹤元件的狀態。
您可以使用 RadioGroupLabel
和 RadioGroupDescription
元件標記每個選項的內容。這樣做將自動透過 aria-labelledby
和 aria-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 的選項 |
空白鍵,當仍未選取任何選項 | 選取第一個選項 |
輸入鍵,在表單中 | 送出表單 |
屬性 | 預設值 | 說明 |
as | div | 字串 | 元件
|
v-model | — | T 選取的值。 |
defaultValue | — | T 當將其用作非受控元件時使用的預設值。 |
by | — | keyof T | ((a: T, z: T) => boolean) 使用此方法可以透過某特定欄位來比較物件,也可以傳遞您自訂的比較函式,以便完全控制如何比較物件。 |
disabled | false | 布林值
|
屬性 | 預設值 | 說明 |
as | div | 字串 | 元件
|
value | — | T | 未定義 目前 |
disabled | false | 布林值
|
name | — | 字串 使用這個元件所在的表單時所使用的名稱。 |
插槽 Prop | 說明 |
active |
是否顯示選項為啟用狀態 (使用滑鼠或鍵盤)。 |
checked |
目前選項是否為已選取值。 |
disabled |
目前選項是否已停用。 |
屬性 | 預設值 | 說明 |
as | label | 字串 | 元件
|
屬性 | 預設值 | 說明 |
as | div | 字串 | 元件
|