單選群組
單選群組能提供您與原生 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
元件的渲染結果,當無線電群組是開啟的,而第二個選項既是 checked
又 active
<!-- 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.Option
的 value
,否則它們將無法相等並導致無線電群組行為不正確。
為使使用同一物件的不同執行個體變得容易,你可以使用 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.Label
和 RadioGroup.Description
元件標記每個選項的內容。這麼做會透過 aria-labelledby
和 aria-describedby
屬性以及自動產生的 id
自動將每個元件連結至其祖先 RadioGroup.Option
元件,改善自訂選擇器的語意和無障礙性。
預設,RatioGroup.Label
會呈現一個 label
元件,而 RadioGroup.Description
會呈現一個 <div>
。這些還可以透過 as
屬性自訂,如下面的 API 文件中所述。
同時請注意 Label
和 Description
可以嵌套。每一個都會指向其最近的祖先元件,無論那個祖先元件是 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.Labelas="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.Descriptionas="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在表單中 | 提交表單 |
Prop | 預設值 | 說明 |
as | div | 字串 | 元件
|
value | — | T | 未定義
|
defaultValue | — | T 使用非受控元件時預設的值。 |
by | — | keyof T | ((a: T, z: T) => boolean) 使用此功能可依特定欄位來比較物件,或傳遞自訂比較的函式,以完全控制如何比較物件。 |
onChange | — | () => void 用於更新 |
disabled | false | 布林值
|
name | — | 字串 使用此元件於表單中時所使用的名稱。 |
Prop | 預設值 | 說明 |
as | div | 字串 | 元件
|
value | — | T | 未定義 目前 |
disabled | false | 布林值
|
Render Prop | 說明 |
active |
選項是否已啟用(使用滑鼠或鍵盤)。 |
checked |
當前選項是否為勾選值。 |
disabled |
當前選項已停用。 |
Prop | 預設值 | 說明 |
as | label | 字串 | 元件
|
Prop | 預設值 | 說明 |
as | div | 字串 | 元件
|