無線電按鈕群組

無線電按鈕群組提供與原生 HTML 無線電輸入相同的功能,但沒有任何樣式。這非常適合為選取器建立客製化使用者介面。

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

npm install @headlessui/react

使用 RadioGroupRadioFieldLabel 元件建立單選群組。

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 元件取得焦點時,所有互動皆會套用。

指令描述

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

循環 RadioGroup 的選項

Space當還沒有選項被選取時

選取已取得焦點的選項

Enter當在表格中時

提交表格

主要的 radio 群組元件。

道具預設值描述
asdiv
字串 | 元件

radio 群組應以何種元素或元件來呈現。

value
T | undefined

RadioGroup 中目前選取的

value。
defaultValue

T

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

by

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

使用此道具透過特定欄位比較物件,或是傳遞你自己的比較函式,以完全控制物件如何被比較。
當你將一個物件傳遞給 value 的道具時,若 by 中有包含 id 欄位,它將預設為 id

呼叫的函數來更新 RadioGroup 值。

disabledfalse
boolean

將此用於停用電台群組及其所有電台。

name
String

在使用中使用的名稱應以何種元素或元件表單中的。

form
String

表單的識別碼,其中應以何種元素或元件屬於。

如果提供了 name 但沒有提供 form,則應以何種元素或元件會將其狀態加入最接近的祖先 form 元素。

資料屬性渲染道具描述
value

defaultValue

選定值。

每個可選項的元件。

道具預設值描述
asspan
字串 | 元件

radio 群組radio來呈現。

value
T | undefined

這個 Radio 的值。類型應該與 RadioGroup 元件中 value 的類型相符。

disabledfalse
布林值

是否radio已停用。.

autoFocusfalse
布林值

是否radio在第一次渲染時是否應該接收焦點。

資料屬性渲染道具描述
data-checkedchecked

布林值

是否radio已勾選。

data-disableddisabled

布林值

是否radio已停用。

data-focusfocus

布林值

是否radio已對焦。

data-hoverhover

布林值

是否radio已懸停。

data-autofocusautofocus

布林值

autoFocus 道具是否已設定為 true

如果您有興趣預先設計的 Tailwind CSS 無頭 UI 電台群組範例請查看 Tailwind UI — 我們打造的精美設計與專業製作元件合輯。

這是一種很好的方式,可以支持我們在像這個一樣的開源專案上的工作,並使我們能夠改進它們並保持它們得到良好的維護。