單選群組

單選群組能提供您與原生 HTML 單選輸入相同的功能,而不具備任何造型。非常適合建立選擇器的自訂 UI。

要開始使用,請透過 npm 安裝 Headless UI

npm install @headlessui/react

單選群組是透過 單選群組標籤選項元件建構。

按一下某個選項將會選取它,當單選群組獲得焦點時,箭頭鍵將會變更選取的選項。

import { useState } from 'react' import { RadioGroup } from '@headlessui/react' function MyRadioGroup() { let [plan, setPlan] = useState('startup') return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> <RadioGroup.Option value="startup"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Startup</span> )} </RadioGroup.Option> <RadioGroup.Option value="business"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Business</span> )} </RadioGroup.Option> <RadioGroup.Option value="enterprise"> {({ checked }) => ( <span className={checked ? 'bg-blue-200' : ''}>Enterprise</span> )} </RadioGroup.Option> </RadioGroup> ) }

Headless UI 會追蹤每個元件的許多狀態,例如目前已勾選哪一個單選群組選項、浮動選單是開啟或關閉狀態,或目前透過鍵盤設定焦點於選單中的哪個項目。

但是,因為這些元件是 Headless 的,而且出廠時完全沒有造型,所以除非您自己針對每種狀態提供想要的造型,否則您無法在 UI 中「看見」這些資訊。

每個元件都會透過 Render Props 揭露其目前狀態的資訊,您可以使用這些資訊有條件地套用不同的造型或呈現不同的內容。

例如,選項元件會揭露一個 active 狀態,告訴您選項目前是否已透過滑鼠或鍵盤獲得焦點,並揭露一個 checked 狀態,告訴您該選項是否符合 單選群組目前 value

import { useState, Fragment } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const plans = ['Statup', 'Business', 'Enterprise'] function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( /* Use the `active` state to conditionally style the active option. */ /* Use the `checked` state to conditionally style the checked option. */ <RadioGroup.Option key={plan} value={plan} as={Fragment}>
{({ active, checked }) => (
<li className={`${
active ? 'bg-blue-500 text-white' : 'bg-white text-black'
}
`
}
>
{checked && <CheckIcon />}
{plan} </li> )} </RadioGroup.Option> ))} </RadioGroup> ) }

有關每個元件的完整 Render Prop API,請參閱 元件 API 文件

每個元件也會公開它目前狀態的資訊透過一個 data-headlessui-state 屬性,你可以使用它有條件地套用不同的樣式。

當 在 渲染屬性 API 中的任何狀態是 true,它們會在這個屬性中列為空白分隔的字串,所以你可以鎖定它們,使用 CSS 屬性選擇器 透過格式 [attr~=value]

例如,這是 RadioGroup 元件包含一些子 RadioGroup.Option 元件的渲染結果,當無線電群組是開啟的,而第二個選項既是 checkedactive

<!-- Rendered `RadioGroup` --> <div role="radiogroup"> <li data-headlessui-state="">Statup</li> <li data-headlessui-state="active checked">Business</li> <li data-headlessui-state="">Enterprise</li> </div>

如果你正在使用 Tailwind CSS,你可以使用 @headlessui/tailwindcss 外掛程式透過修改器,如 ui-open:*ui-active:* 來鎖定這個屬性

import { useState, Fragment } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const plans = ['Statup', 'Business', 'Enterprise'] function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return ( <RadioGroup value={plan} onChange={setPlan}> <RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan} value={plan}
className="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon className="hidden ui-checked:block" />
{plan} </RadioGroup.Option> ))} </RadioGroup> ) }

與原生 HTML 表單控制元件只允許你提供字串作為值的不同,Headless UI 支援繫結複雜的物件。

import { useState } from 'react' import { RadioGroup } from '@headlessui/react'
const plans = [
{ id: 1, name: 'Startup' },
{ id: 2, name: 'Business' },
{ id: 3, name: 'Enterprise' },
]
function MyRadioGroup() { const [plan, setPlan] = useState(plans[0]) return (
<RadioGroup value={plan} onChange={setPlan}>
<RadioGroup.Label>Plan:</RadioGroup.Label> {plans.map((plan) => (
<RadioGroup.Option key={plan.id} value={plan}>
{plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }

當繫結物件作為值時,很重要確保你使用 相同的物件執行個體 作為 RadioGroup 和對應的 RadioGroup.Optionvalue,否則它們將無法相等並導致無線電群組行為不正確。

為使使用同一物件的不同執行個體變得容易,你可以使用 by 屬性依據特定欄位比較物件而非比較物件識別碼

import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
function PlanPicker({ checkedPlan, onChange }) {
return (
<RadioGroup value={checkedPlan} by="id" onChange={onChange}>
<RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }

你也可以傳遞你自己的比較函式給 by 屬性,如果你想要完全控制物件如何被比較

import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
function comparePlans(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
function PlanPicker({ checkedPlan, onChange }) { return (
<RadioGroup value={checkedPlan} by={comparePlans} onChange={onChange}>
<RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> ) }

如果你將 name 屬性加到你的列表框,隱藏的 input 元素將被渲染並與你的選取值保持同步。

import { useState } from 'react' import { RadioGroup } from '@headlessui/react' const plans = ['startup', 'business', 'enterprise'] function Example() { const [plan, setPlan] = useState(plans[0]) return ( <form action="/billing" method="post">
<RadioGroup value={plan} onChange={setPlan} name="plan">
<RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan} value={plan}> {plan} </RadioGroup.Option> ))} </RadioGroup> <button>Submit</button> </form> ) }

這讓你可以在原生 HTML <form> 中使用無線電群組,並進行傳統表單提交,彷彿你的無線電群組是原生 HTML 表單控制元件。

基本值,如字串,將渲染為包含該值的單一隱藏輸入,但複雜值,如物件,將使用方括弧表示法編碼至多個輸入中作為名稱。

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

若您提供一個 defaultValue 屬性給 RadioGroup 而不是 value,headless UI 將會在內部追蹤其狀態供您使用,讓您可以當作 不受控元件 使用。

import { RadioGroup } from '@headlessui/react' const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ] function Example() { return ( <form action="/companies" method="post">
<RadioGroup name="plan" defaultValue={plans[0]}>
<RadioGroup.Label>Plan</RadioGroup.Label> {plans.map((plan) => ( <RadioGroup.Option key={plan.id} value={plan}> {plan.name} </RadioGroup.Option> ))} </RadioGroup> <button>Submit</button> </form> ) }

當組合方塊與HTML 表單一起使用或與透過 FormData 來收集狀態而非使用 React 狀態的方式時,這可以簡化您的程式碼。

如果您需要執行任何副作用,您所提供的任何 onChange 屬性仍然會在元件值改變時被呼叫,但您無需使用它自行追蹤元件的狀態。

您可以使用 RadioGroup.LabelRadioGroup.Description 元件標記每個選項的內容。這麼做會透過 aria-labelledbyaria-describedby 屬性以及自動產生的 id 自動將每個元件連結至其祖先 RadioGroup.Option 元件,改善自訂選擇器的語意和無障礙性。

預設,RatioGroup.Label 會呈現一個 label 元件,而 RadioGroup.Description 會呈現一個 <div>。這些還可以透過 as 屬性自訂,如下面的 API 文件中所述。

同時請注意 LabelDescription 可以嵌套。每一個都會指向其最近的祖先元件,無論那個祖先元件是 RadioGroup.Option 或根部的 RadioGroup 本身。

import { useState } from 'react' import { RadioGroup } from '@headlessui/react' function MyRadioGroup() { const [selected, setSelected] = useState('startup') return ( <RadioGroup value={selected} onChange={setSelected}> {/* This label is for the root `RadioGroup`. */}
<RadioGroup.Label className="sr-only">Plan</RadioGroup.Label>
<div className="rounded-md bg-white"> <RadioGroup.Option value="startup" className={({ checked }) => ` ${checked ? 'border-indigo-200 bg-indigo-50' : 'border-gray-200'} relative flex border p-4 `} > {({ checked }) => ( <div className="flex flex-col"> {/* This label is for the `RadioGroup.Option`. */}
<RadioGroup.Label
as="span"
className={`${
checked ? 'text-indigo-900' : 'text-gray-900'
} block text-sm font-medium`}
>
Startup
</RadioGroup.Label>
{/* This description is for the `RadioGroup.Option`. */}
<RadioGroup.Description
as="span"
className={`${
checked ? 'text-indigo-700' : 'text-gray-500'
} block text-sm`}
>
Up to 5 active job postings
</RadioGroup.Description>
</div> )} </RadioGroup.Option> </div> </RadioGroup> ) }

按一下 RadioGroup.Option 將會選取它。

RadioGroup 元件聚焦時,所有互動都會套用。

指令說明

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

循環切換 RadioGroup 的選項

Space當尚未選擇任何選項時

選擇第一個選項

Enter在表單中

提交表單

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

主要的 Radio Group 元件。

Prop預設值說明
asdiv
字串 | 元件

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

value
T | 未定義

RadioGroup 中目前選取的值。

defaultValue
T

使用非受控元件時預設的值。

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

使用此功能可依特定欄位來比較物件,或傳遞自訂比較的函式,以完全控制如何比較物件。

onChange
() => void

用於更新 RadioGroup 值所呼叫的函式。

disabledfalse
布林值

RadioGroup 及其所有的 RadioGroup.Option 是否已停用。

name
字串

使用此元件於表單中時所使用的名稱。

可選取每個選項的包裝組件。

Prop預設值說明
asdiv
字串 | 元件

RadioGroup.Option 應呈現為的元素或組件。

value
T | 未定義

目前 RadioGroup.Option 的值。類型應與 RadioGroup 組件中 value 的類型相符。

disabledfalse
布林值

RadioGroup.Option 已停用。

Render Prop說明
active

布林值

選項是否已啟用(使用滑鼠或鍵盤)。

checked

布林值

當前選項是否為勾選值。

disabled

布林值

當前選項已停用。

呈現 id 屬性自動產生,並透過 aria-labelledby 屬性連結到最近的父物件 RadioGroupRadioGroup.Option 組件的元素。

Prop預設值說明
aslabel
字串 | 元件

RadioGroup.Label 應呈現為的元素或組件。

呈現 id 屬性自動產生,並透過 aria-describedby 屬性連結到最近的父物件 RadioGroupRadioGroup.Option 組件的元素。

Prop預設值說明
asdiv
字串 | 元件

RadioGroup.Description 應呈現為的元素或組件。

如果你有興趣使用 Headless UI 和 Tailwind CSS 預先設計的組件範例,請查看Tailwind UI,這是由我們打造的漂亮設計以及技術精湛的組件集合。

支援我們在像這個一樣的開放原始碼專案上作業,讓我們的專案可以改善並維護良好,是一個很棒的方式。