无线电组

无线电组提供与原生 HTML 无线电输入相同的功能,但没有样式。它们非常适合构建自定义选择器 UI。

要开始,请通过 npm 安装无头 UI。

请注意,**此库仅支持 Vue 3**。

npm install @headlessui/vue

无线电组使用 RadioGroupRadioGroupLabelRadioGroupOption 组件构建。

单击选项将选择它,当无线电组处于焦点时,箭头键将更改所选选项。

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-slot="{ checked }" value="startup"> <span :class="checked ? 'bg-blue-200' : ''">Startup</span> </RadioGroupOption> <RadioGroupOption v-slot="{ checked }" value="business"> <span :class="checked ? 'bg-blue-200' : ''">Business</span> </RadioGroupOption> <RadioGroupOption v-slot="{ checked }" value="enterprise"> <span :class="checked ? 'bg-blue-200' : ''">Enterprise</span> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plan = ref('startup') </script>

无头 UI 跟踪每个组件的许多状态,例如当前选中的哪个无线电组选项,弹出窗口是打开还是关闭,或者哪个无线电组项目当前通过键盘处于活动状态。

但由于组件是无头的,并且在开箱即用时完全没有样式,因此在您自己提供每个状态所需的样式之前,您无法在 UI 中看到此信息。

每个组件都通过插槽属性公开其当前状态的信息,您可以使用这些属性有条件地应用不同的样式或呈现不同的内容。

例如,RadioGroupOption 组件公开了一个 active 状态,它告诉您该项目当前是否通过鼠标或键盘处于焦点。

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <!-- Use the `active` state to conditionally style the active option. --> <!-- Use the `checked` state to conditionally style the checked option. --> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan" as="template"
v-slot="{ active, checked }"
>
<li :class="{
'bg-blue-500 text-white': active,
'bg-white text-black': !active,
}"
>
<CheckIcon v-show="checked" />
{{ plan }} </li> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const plans = ['Startup', 'Business', 'Enterprise'] const plan = ref(plans[0]) </script>

有关所有可用插槽属性的完整列表,请参阅组件 API 文档

每个组件还通过 data-headlessui-state 属性公开其当前状态的信息,您可以使用它有条件地应用不同的样式。

插槽属性 API中的任何状态为 true 时,它们将作为空格分隔的字符串列在这个属性中,因此您可以使用CSS 属性选择器[attr~=value] 的形式进行定位。

例如,以下是如何在无线电组打开并且第二个项目处于 active 状态时,RadioGroup 组件及其一些子 RadioGroupOption 组件呈现的。

<!-- Rendered `RadioGroup` --> <ul data-headlessui-state="open"> <li data-headlessui-state="">Wade Cooper</li> <li data-headlessui-state="active selected">Arlene Mccoy</li> <li data-headlessui-state="">Devon Webb</li> </ul>

如果您使用的是 Tailwind CSS,您可以使用 @headlessui/tailwindcss 插件以使用修饰符(如 ui-open:*ui-active:*)定位此属性。

<template> <RadioGroup v-model="plan"> <RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan" as="template" > <li
class="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon class="hidden ui-checked:block" />
{{ plan }} </li> </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const plans = ['Startup', 'Business', 'Enterprise'] const plan = ref(plans[0]) </script>

与仅允许您提供字符串作为值的原生 HTML 表单控件不同,无头 UI 也支持绑定复杂对象。

<template>
<RadioGroup v-model="plan">
<RadioGroupLabel>Plan</RadioGroupLabel>
<RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan">
{{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue'
const plans = [
{ id: 1, name: 'Startup' },
{ id: 2, name: 'Business' },
{ id: 3, name: 'Enterprise' },
]
const plan = ref(plans[1])
</script>

在将对象绑定为值时,务必确保使用相同实例的对象作为 RadioGroupvalue 以及相应的 RadioGroupOption,否则它们将无法相等,导致无线电组行为不当。

为了更轻松地使用同一对象的不同实例,您可以使用 by 属性通过特定字段比较对象,而不是比较对象标识。

<template> <RadioGroup :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
by="id"
>
<RadioGroupLabel>Assignee</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan"> {{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue']) const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ] </script>

如果您希望完全控制如何比较对象,也可以将您自己的比较函数传递给 by 属性。

<template> <RadioGroup :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
:by="comparePlans"
>
<RadioGroupLabel>Assignee</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan.id" :value="plan"> {{ plan.name }} </RadioGroupOption> </RadioGroup> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue'])
function comparePlans(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
const plans = [ { id: 1, name: 'Startup' }, { id: 2, name: 'Business' }, { id: 3, name: 'Enterprise' }, ]
</script>

如果您将 name 属性添加到您的列表框,将渲染隐藏的 input 元素并与您的选择值保持同步。

<template> <form action="/billing" method="post">
<RadioGroup v-model="plan" name="plan">
<RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan"> {{ plan }} </RadioGroupOption> </RadioGroup> <button>Submit</button> </form> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plans = ['startup', 'business', 'enterprise'] const plan = ref(plans[0]) </script>

这使您可以在原生 HTML <form> 中使用无线电组,并进行传统的表单提交,就像您的无线电组是原生 HTML 表单控件一样。

基本的字符串值将被渲染为包含该值的单个隐藏输入,但复杂的值(如对象)将使用方括号表示法对名称进行编码,并被编码为多个输入。

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

如果您向 RadioGroup 提供 defaultValue 属性而不是 value 属性,无头 UI 将为您内部跟踪其状态,使您可以将其用作 非受控组件

<template> <form action="/billing" method="post">
<RadioGroup name="plan" :defaultValue="plans[0]">
<RadioGroupLabel>Plan</RadioGroupLabel> <RadioGroupOption v-for="plan in plans" :key="plan" :value="plan"> {{ plan }} </RadioGroupOption> </RadioGroup> <button>Submit</button> </form> </template> <script setup> import { RadioGroup, RadioGroupLabel, RadioGroupOption, } from '@headlessui/vue' const plans = ['startup', 'business', 'enterprise'] </script>

当将组合框与 HTML 表单一起使用或使用使用 FormData 收集其状态而不是使用 React 状态跟踪其状态的表单 API 时,这可以简化您的代码。

您提供的任何 @update:modelValue 属性仍然会在组件的值发生变化时被调用,以防您需要运行任何副作用,但您无需使用它来自己跟踪组件的状态。

您可以使用 RadioGroupLabelRadioGroupDescription 组件来标记每个选项的内容。这样做会自动通过 aria-labelledbyaria-describedby 属性以及自动生成的 id 将每个组件链接到其祖先 RadioGroupOption 组件,从而提高自定义选择器的语义和可访问性。

默认情况下,RatioGroupLabel 渲染一个 label 元素,RadioGroupDescription 渲染一个 <div>。这些也可以使用 as 属性进行自定义,如以下 API 文档中所述。

另请注意,LabelDescription 可以嵌套。每个 LabelDescription 都将引用其最近的祖先组件,无论该祖先组件是 RadioGroupOption 还是根 RadioGroup 本身。

<template> <RadioGroup v-model="plan"> <!-- This label is for the root `RadioGroup` -->
<RadioGroupLabel class="sr-only">Plan</RadioGroupLabel>
<div class="rounded-md bg-white"> <RadioGroupOption value="startup" as="template" v-slot="{ checked }"> <div :class='checked ? "bg-indigo-50 border-indigo-200" : "border-gray-200"' class="relative flex border p-4" > <div class="flex flex-col"> <!-- This label is for the `RadioGroupOption` -->
<RadioGroupLabel as="template">
<span
:class='checked ? "text-indigo-900" : "text-gray-900"'
class="block text-sm font-medium"
>Startup</span
>
</RadioGroupLabel>
<!-- This description is for the `RadioGroupOption` -->
<RadioGroupDescription as="template">
<span
:class='checked ? "text-indigo-700" : "text-gray-500"'
class="block text-sm"
>Up to 5 active job postings</span
>
</RadioGroupDescription>
</div> </div> </RadioGroupOption> </div> </RadioGroup> </template> <script setup> import { ref } from 'vue' import { RadioGroup, RadioGroupLabel, RadioGroupOption, RadioGroupDescription, } from '@headlessui/vue' const plan = ref('startup') </script>

单击 RadioGroupOption 将选择它。

RadioGroup 组件处于焦点时,所有交互都适用。

命令描述

向下箭头 或者 向上箭头 或者 向左箭头 或者 向右箭头

循环遍历无线电组的选项

空格 当没有选择任何选项时

选择第一个选项

回车 当在表单中时

提交表单

所有相关的 ARIA 属性都会被自动管理。

主要的无线电组组件。

属性默认值描述
asdiv
String | Component

RadioGroup 应该渲染成的元素或组件。

v-model
T

所选值。

defaultValue
T

在用作非受控组件时的默认值。

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

使用它通过特定字段比较对象,或传递您自己的比较函数以完全控制如何比较对象。

disabledfalse
boolean

RadioGroup 及其所有 RadioGroupOption 是否被禁用。

每个可选选项的包装组件。

属性默认值描述
asdiv
String | Component

RadioGroupOption 应该渲染成的元素或组件。

value
T | undefined

当前 RadioGroupOption 的值。类型应与 RadioGroup 组件中 value 的类型匹配。

disabledfalse
boolean

RadioGroupOption 是否被禁用。

name
String

在表单中使用此组件时使用的名称。

插槽属性描述
active

Boolean

选项是否处于活动状态(使用鼠标或键盘)。

checked

Boolean

当前选项是否为选中值。

disabled

boolean

当前选项是否禁用。

渲染一个元素,其 id 属性会自动生成,然后通过 aria-labelledby 属性链接到其最近的祖先 RadioGroupRadioGroupOption 组件。

属性默认值描述
as标签
String | Component

RadioGroupLabel 应渲染的元素或组件。

渲染一个元素,其 id 属性会自动生成,然后通过 aria-describedby 属性链接到其最近的祖先 RadioGroupRadioGroupOption 组件。

属性默认值描述
asdiv
String | Component

RadioGroupDescription 应渲染的元素或组件。

如果您对使用 Headless UI 和 Tailwind CSS 的预先设计的组件示例感兴趣,请查看 **Tailwind UI** - 我们精心打造的一组美观且设计精良的组件。

这是支持我们开源项目工作的好方法,使我们能够改进这些项目并保持良好的维护。