無線電按鈕群組
無線電按鈕群組提供與原生 HTML 無線電輸入相同的功能,但沒有任何樣式。這非常適合為選取器建立客製化使用者介面。
開始使用,請透過 npm 安裝 Headless UI
npm install @headlessui/react
使用 RadioGroup
、Radio
、Field
和 Label
元件建立單選群組。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan} className="flex items-center gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400"
>
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" />
</Radio>
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
)
}
Headless UI 會追蹤各個元件的許多狀態,例如目前勾選的單選群組選項、彈出視窗是開啟還是關閉,或目前使用鍵盤對焦於選單中的哪個項目。
然而由於這些元件在預設情況下沒有抬頭且完全沒有樣式,因此在您自行提供各個狀態希望使用的樣式之前,您無法在使用介面中看到這些資訊。
調整 Headless UI 元件不同狀態最容易的方法是使用各個元件公開的 data-*
屬性。
例如,Radio
元件公開一個 data-checked
屬性,告訴您單選按鈕目前是否已勾選,以及一個 data-disabled
屬性,告訴您單選按鈕目前是否已停用。
<!-- Rendered `Radio` -->
<span role="radio" data-checked data-disabled>
<!-- ... -->
</span>
使用 CSS 屬性選擇器 根據這些資料屬性的存在有條件地套用樣式。如果您使用的是 Tailwind CSS,資料屬性修改器 能讓您輕鬆辦到
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan.name} disabled={!plan.available} className="flex items-center gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400 data-[disabled]:bg-gray-100" >
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" /> </Radio>
<Label className="data-[disabled]:opacity-50">{plan.name}</Label> </Field>
))}
</RadioGroup>
)
}
各個元件也透過 Render Props 公開其目前狀態資訊,您可以使用此資訊有條件地套用不同樣式或呈現不同內容。
例如,Radio
元件公開一個 checked
狀態,告訴您單選按鈕目前是否已勾選,以及一個 disabled
狀態,告訴您單選按鈕目前是否已停用。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import clsx from 'clsx'
import { Fragment, useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan.name} disabled={!plan.available} className="flex items-center gap-2">
<Radio as={Fragment} value={plan}> {({ checked, disabled }) => ( <span
className={clsx(
'group flex size-5 items-center justify-center rounded-full border',
checked ? 'bg-blue-400' : 'bg-white', disabled && 'bg-gray-100' )}
>
{checked && <span className="size-2 rounded-full bg-white" />} </span>
)}
</Radio>
<Label as={Fragment}> {({ disabled }) => <label className={disabled && 'opacity-50'}>{plan.name}</label>} </Label> </Field>
))}
</RadioGroup>
)
}
請參閱 元件 API,取得所有可用 Render Props 清單。
在 字段
中使用 說明
組件,透過 aria-describedby
屬性將其自動與 無線電
做關聯。
import { Description, Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', description: '12GB, 6 CPUs, 256GB SSD disk' }, { name: 'Business', description: '16GB, 8 CPUs, 512GB SSD disk' }, { name: 'Enterprise', description: '32GB, 12 CPUs, 1TB SSD disk' },]
function Example() {
let [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size">
{plans.map((plan) => (
<Field key={plan} className="flex items-baseline gap-2">
<Radio
value={plan}
className="group flex size-5 items-center justify-center rounded-full border bg-white data-[checked]:bg-blue-400"
>
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" />
</Radio>
<div>
<Label>{plan.name}</Label>
<Description className="opacity-50">{plan.description}</Description> </div>
</Field>
))}
</RadioGroup>
)
}
如果您將 名稱
屬性新增到 無線電群組
,將會呈現一個隱藏的 輸入
元素,並與無線電群組狀態保持同步。
import { Field, Fieldset, Label, Legend, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<form action="/plans" method="post"> <Fieldset>
<Legend>Server size</Legend>
<RadioGroup name="plan" value={selected} onChange={setSelected}> {plans.map((plan) => (
<Field key={plan}>
<Radio value={plan} />
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
</Fieldset>
<button>Submit</button>
</form> )
}
這可讓您在原生的 HTML <form>
內部使用無線電群組,並執行傳統的表單提交,就像無線電群組是原生的 HTML 表單控制項一樣。
像字串等基本值將被呈現為單一隱藏輸入,其中包含值,但像是物件等複雜值將使用名稱的方括弧表示法編碼成多個輸入。
<!-- Rendered hidden input -->
<input type="hidden" name="plan" value="startup" />
如果您遺漏 值
屬性,Headless UI 將會為您在內部追蹤其狀態,讓您可以將其當作 不受控元件 使用。
在不受控時,使用 預設值
屬性提供初始值給 無線電群組
。
import { useState } from 'react'
import { RadioGroup, Radio, Fieldset, Legend, Field, Label } from '@headlessui/react'
const plans = ['Startup', 'Business', 'Enterprise']
function Example() {
return (
<form action="/plans" method="post">
<Fieldset>
<Legend>Server size</Legend>
<RadioGroup name="plan" defaultValue={plans[0]}> {plans.map((plan) => (
<Field key={plan}>
<Radio value={plan} />
<Label>{plan}</Label>
</Field>
))}
</RadioGroup>
</Fieldset>
</form>
)
}
當將複合方塊 與 HTML 樣式搭配使用,或是與收集其狀態使用 FormData 而不是使用 React 狀態追蹤的表單 API 搭配使用時,這可簡化您的代碼。
當元件的值變更時,您提供的任何 onChange
屬性仍然會被呼叫,以防您需要執行任何副作用,但您不需要自行使用它來追蹤元件的狀態。
與僅允許您提供字串作為值的原生 HTML 表單控制項不同,Headless UI 也支援將複雜的物件繫結為值。
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [ { id: 1, name: 'Startup', available: true }, { id: 2, name: 'Business', available: true }, { id: 3, name: 'Enterprise', available: false },]
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} /> <Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
將物件繫結為值時,務必確保您使用物件的 相同執行個體 作為 無線電群組
的 值
,以及對應的 無線電
,否則它們將無法相等並導致無線電群組行為不正確。
若要更輕鬆地使用同一物件的不同執行個體,您可以使用 by
屬性,透過特定欄位比較物件,而不是依物件識別碼進行比較。
當您將物件傳遞到 值
屬性時,在存在 id
時,by
預設為 id
,但您可以設定為您喜歡的任何欄位
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [
{ name: 'Startup', available: true },
{ name: 'Business', available: true },
{ name: 'Enterprise', available: false },
]
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} by="name" onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} />
<Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
如果你想要完全控制物件的比較方式,也可以將你自己的比較函式傳遞給 by
道具
import { Field, Label, Radio, RadioGroup } from '@headlessui/react'
import { useState } from 'react'
const plans = [ { id: 1, name: 'Startup', available: true }, { id: 2, name: 'Business', available: true }, { id: 3, name: 'Enterprise', available: false },]
function comparePlans(a, b) { return a.name.toLowerCase() === b.name.toLowerCase()}
function Example() {
const [selected, setSelected] = useState(plans[0])
return (
<RadioGroup value={selected} by={comparePlans} onChange={setSelected} aria-label="Server size"> {plans.map((plan) => (
<Field key={plan.id}>
<Radio value={plan} disabled={!plan.available} />
<Label>{plan.name}</Label>
</Field>
))}
</RadioGroup>
)
}
當 Radio
元件取得焦點時,所有互動皆會套用。
指令 | 描述 |
向下箭頭或向上箭頭或向左箭頭或向右箭頭 | 循環 |
Space當還沒有選項被選取時 | 選取已取得焦點的選項 |
Enter當在表格中時 | 提交表格 |
道具 | 預設值 | 描述 |
as | div | 字串 | 元件 radio 群組應以何種元素或元件來呈現。 |
value | — | T | undefined
|
value。 | — | defaultValue T |
在使用非受控元件時預設的 | — | value。 by keyof T | ((a: T, z: T) => boolean) |
使用此道具透過特定欄位比較物件,或是傳遞你自己的比較函式,以完全控制物件如何被比較。 | — | 當你將一個物件傳遞給 呼叫的函數來更新 |
disabled | false | boolean 將此用於停用電台群組及其所有電台。 |
name | — | String 在使用中使用的名稱應以何種元素或元件表單中的。 |
form | — | String 表單的識別碼,其中應以何種元素或元件屬於。 如果提供了 |
資料屬性 | 渲染道具 | 描述 |
— | value |
選定值。 |
道具 | 預設值 | 描述 |
as | span | 字串 | 元件 radio 群組radio來呈現。 |
value | — | T | undefined 這個 |
disabled | false | 布林值 是否radio已停用。. |
autoFocus | false | 布林值 是否radio在第一次渲染時是否應該接收焦點。 |
資料屬性 | 渲染道具 | 描述 |
data-checked | checked |
是否radio已勾選。 |
data-disabled | disabled |
是否radio已停用。 |
data-focus | focus |
是否radio已對焦。 |
data-hover | hover |
是否radio已懸停。 |
data-autofocus | autofocus |
|
如果您有興趣預先設計的 Tailwind CSS 無頭 UI 電台群組範例,請查看 Tailwind UI — 我們打造的精美設計與專業製作元件合輯。
這是一種很好的方式,可以支持我們在像這個一樣的開源專案上的工作,並使我們能夠改進它們並保持它們得到良好的維護。