单选按钮组
单选按钮组为您提供与原生 HTML 单选按钮输入相同的功能,但没有任何样式。它们非常适合构建自定义选择器 UI。
要开始使用,请通过 npm 安装 Headless UI
npm install @headlessui/react
单选按钮组是使用 RadioGroup
、RadioGroup.Label
和 RadioGroup.Option
组件构建的。
单击选项将选择它,当单选按钮组获得焦点时,箭头键将更改所选选项。
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 会跟踪有关每个组件的许多状态,例如当前选中哪个单选按钮组选项、弹出窗口是打开还是关闭,或者菜单中哪个项目当前通过键盘处于活动状态。
但是,因为组件是无状态的并且在开箱即用时完全没有样式,所以您无法看到 UI 中的这些信息,直到您自己为每个状态提供所需的样式。
每个组件都通过 渲染道具 公开其当前状态的信息,您可以使用这些道具来有条件地应用不同的样式或渲染不同的内容。
例如,RadioGroup.Option
组件公开了一个 active
状态,它告诉您该选项是否通过鼠标或键盘获得焦点,以及一个 checked
状态,它告诉您该选项是否与 RadioGroup
的当前 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> ) }
有关每个组件的完整渲染道具 API,请参阅 组件 API 文档。
每个组件还通过 data-headlessui-state
属性公开其当前状态的信息,您可以使用该属性来有条件地应用不同的样式。
当 渲染道具 API 中的任何状态为 true
时,它们将在此属性中列出为以空格分隔的字符串,以便您可以使用 CSS 属性选择器 以 [attr~=value]
的形式对其进行定位。
例如,以下是如何在单选按钮组打开并且第二个选项同时为 checked
和 active
时,RadioGroup
组件及其一些子 RadioGroup.Option
组件的渲染结果。
<!-- 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
的 value
以及相应的 RadioGroup.Option
,否则它们将无法相等,会导致单选按钮组行为异常。
为了更轻松地使用同一对象的多个实例,您可以使用 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" />
如果您向 RadioGroup
提供 defaultValue
属性而不是 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 状态的表单 API 一起使用时,这可以简化您的代码。
您提供的任何 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
组件获得焦点时,所有交互都适用。
命令 | 描述 |
向下箭头 或 向上箭头 或 向左箭头 或 向右箭头 | 循环遍历单选按钮组的选项 |
空格键 当尚未选择任何选项时 | 选择第一个选项 |
回车键 当在表单中时 | 提交表单 |
属性 | 默认值 | 描述 |
as | div | String | Component
|
value | — | T | undefined
|
defaultValue | — | T 作为非受控组件使用时的默认值。 |
by | — | keyof T | ((a: T, z: T) => boolean) 使用此属性通过特定字段比较对象,或者传递自己的比较函数以完全控制对象的比较方式。 |
onChange | — | () => void 用于更新 |
disabled | false | boolean
|
name | — | String 在表单中使用此组件时使用的名称。 |
属性 | 默认值 | 描述 |
as | div | String | Component
|
value | — | T | undefined 当前 |
disabled | false | boolean
|
渲染属性 | 描述 |
active |
选项是否处于活动状态(使用鼠标或键盘)。 |
checked |
当前选项是否为选中值。 |
disabled |
当前选项是否被禁用。 |
属性 | 默认值 | 描述 |
as | label | String | Component
|
属性 | 默认值 | 描述 |
as | div | String | Component
|