无线电组
无线电组提供与原生 HTML 无线电输入相同的功能,但没有样式。它们非常适合构建自定义选择器 UI。
要开始,请通过 npm 安装无头 UI。
请注意,**此库仅支持 Vue 3**。
npm install @headlessui/vue
无线电组使用 RadioGroup
、RadioGroupLabel
和 RadioGroupOption
组件构建。
单击选项将选择它,当无线电组处于焦点时,箭头键将更改所选选项。
<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>
在将对象绑定为值时,务必确保使用相同实例的对象作为 RadioGroup
的 value
以及相应的 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
属性仍然会在组件的值发生变化时被调用,以防您需要运行任何副作用,但您无需使用它来自己跟踪组件的状态。
您可以使用 RadioGroupLabel
和 RadioGroupDescription
组件来标记每个选项的内容。这样做会自动通过 aria-labelledby
和 aria-describedby
属性以及自动生成的 id
将每个组件链接到其祖先 RadioGroupOption
组件,从而提高自定义选择器的语义和可访问性。
默认情况下,RatioGroupLabel
渲染一个 label
元素,RadioGroupDescription
渲染一个 <div>
。这些也可以使用 as
属性进行自定义,如以下 API 文档中所述。
另请注意,Label
和 Description
可以嵌套。每个 Label
和 Description
都将引用其最近的祖先组件,无论该祖先组件是 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
组件处于焦点时,所有交互都适用。
命令 | 描述 |
向下箭头 或者 向上箭头 或者 向左箭头 或者 向右箭头 | 循环遍历无线电组的选项 |
空格 当没有选择任何选项时 | 选择第一个选项 |
回车 当在表单中时 | 提交表单 |
属性 | 默认值 | 描述 |
as | div | String | Component
|
v-model | — | T 所选值。 |
defaultValue | — | T 在用作非受控组件时的默认值。 |
by | — | keyof T | ((a: T, z: T) => boolean) 使用它通过特定字段比较对象,或传递您自己的比较函数以完全控制如何比较对象。 |
disabled | false | boolean
|
属性 | 默认值 | 描述 |
as | div | String | Component
|
value | — | T | undefined 当前 |
disabled | false | boolean
|
name | — | String 在表单中使用此组件时使用的名称。 |
插槽属性 | 描述 |
active |
选项是否处于活动状态(使用鼠标或键盘)。 |
checked |
当前选项是否为选中值。 |
disabled |
当前选项是否禁用。 |
属性 | 默认值 | 描述 |
as | 标签 | String | Component
|
属性 | 默认值 | 描述 |
as | div | String | Component
|