unnamed-ui
输入控件

Radio

单选框组件,用于在多个选项中选择单个值

import { Radio, RadioGroup } from "@/components/composed/radio/radio";

export function RadioDemo() {
  return (
    <div className="flex flex-col gap-6">
      <RadioGroup defaultValue="option1">
        <Radio value="option1" id="option1">
          选项 1
        </Radio>
        <Radio value="option2" id="option2">
          选项 2
        </Radio>
        <Radio value="option3" id="option3" disabled>
          选项 3(禁用)
        </Radio>
      </RadioGroup>
    </div>
  );
}

Radio 单选框组件用于在多个选项中选择单个值,支持受控和非受控模式,提供丰富的自定义选项和灵活的布局方式。

概述

  • 单选限制:确保同一组中只能选择一个选项
  • 受控/非受控:支持两种使用模式,灵活适配不同场景
  • 多种配置:支持通过 options 数组或 children 两种方式配置
  • 自定义样式:支持 classNames 和 styles 自定义各部分样式
  • 方向布局:支持水平和垂直两种排列方式
  • 禁用状态:支持整组禁用或单个选项禁用
  • 类型安全:完整的 TypeScript 类型定义

快速开始

import { Radio, RadioGroup } from "@/registry/wuhan/composed/radio/radio";

export function Example() {
  return (
    <RadioGroup defaultValue="option1">
      <Radio value="option1" id="option1">
        选项 1
      </Radio>
      <Radio value="option2" id="option2">
        选项 2
      </Radio>
      <Radio value="option3" id="option3">
        选项 3
      </Radio>
    </RadioGroup>
  );
}

特性

  • 灵活的值类型:value 支持 string、number、boolean 等类型
  • Context 集成:RadioGroup 通过 Context 管理子 Radio 的状态
  • 函数式样式:classNames 和 styles 支持对象或函数形式
  • 完整的事件支持:onChange 回调提供完整的状态变更信息
  • 无障碍支持:完整的 ARIA 属性和键盘导航支持

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/radio.json

代码演示

基础用法

基础的单选框组使用。

import { Radio, RadioGroup } from "@/components/composed/radio/radio";

export function RadioDemo() {
  return (
    <div className="flex flex-col gap-6">
      <RadioGroup defaultValue="option1">
        <Radio value="option1" id="option1">
          选项 1
        </Radio>
        <Radio value="option2" id="option2">
          选项 2
        </Radio>
        <Radio value="option3" id="option3" disabled>
          选项 3(禁用)
        </Radio>
      </RadioGroup>
    </div>
  );
}

受控模式

通过 value 和 onChange 控制选中状态。

当前选中: option1
"use client";

import { useState } from "react";
import { Radio, RadioGroup } from "@/components/composed/radio/radio";

export function RadioControlled() {
  const [value, setValue] = useState("option1");

  return (
    <div className="flex flex-col gap-4">
      <RadioGroup value={value} onChange={(v) => setValue(String(v))}>
        <Radio value="option1" id="controlled-1">
          选项 1
        </Radio>
        <Radio value="option2" id="controlled-2">
          选项 2
        </Radio>
        <Radio value="option3" id="controlled-3">
          选项 3
        </Radio>
      </RadioGroup>
      <div className="text-sm text-gray-500">当前选中: {value}</div>
    </div>
  );
}

Options 配置

通过 options 数组配置选项列表。

import { RadioGroup } from "@/components/composed/radio/radio";

export function RadioGroupOptions() {
  return (
    <RadioGroup
      defaultValue="apple"
      options={[
        { label: "苹果", value: "apple" },
        { label: "橙子", value: "orange" },
        { label: "香蕉", value: "banana" },
        { label: "西瓜(禁用)", value: "watermelon", disabled: true },
      ]}
    />
  );
}

垂直布局

设置 orientation 为 vertical 实现垂直排列。

import { RadioGroup } from "@/components/composed/radio/radio";

export function RadioVertical() {
  return (
    <RadioGroup
      defaultValue="1"
      orientation="vertical"
      options={["选项 1", "选项 2", "选项 3"]}
    />
  );
}

禁用状态

支持禁用整个组或单个选项。

禁用整个组
禁用单个选项
import { Radio, RadioGroup } from "@/components/composed/radio/radio";

export function RadioDisabled() {
  return (
    <div className="flex flex-col gap-6">
      <div>
        <div className="text-sm font-medium mb-3">禁用整个组</div>
        <RadioGroup defaultValue="option1" disabled>
          <Radio value="option1" id="disabled-group-1">
            选项 1
          </Radio>
          <Radio value="option2" id="disabled-group-2">
            选项 2
          </Radio>
          <Radio value="option3" id="disabled-group-3">
            选项 3
          </Radio>
        </RadioGroup>
      </div>

      <div>
        <div className="text-sm font-medium mb-3">禁用单个选项</div>
        <RadioGroup defaultValue="option1">
          <Radio value="option1" id="disabled-item-1">
            选项 1
          </Radio>
          <Radio value="option2" id="disabled-item-2" disabled>
            选项 2(禁用)
          </Radio>
          <Radio value="option3" id="disabled-item-3">
            选项 3
          </Radio>
        </RadioGroup>
      </div>
    </div>
  );
}

受控组

RadioGroup 的受控模式。

已选择: react
"use client";

import { useState } from "react";
import { RadioGroup } from "@/components/composed/radio/radio";

export function RadioGroupControlled() {
  const [value, setValue] = useState<string>("react");

  return (
    <div className="flex flex-col gap-4">
      <RadioGroup
        value={value}
        onChange={(v) => setValue(String(v))}
        options={[
          { label: "React", value: "react" },
          { label: "Vue", value: "vue" },
          { label: "Angular", value: "angular" },
          { label: "Svelte", value: "svelte" },
        ]}
      />
      <div className="text-sm text-gray-500">已选择: {value}</div>
    </div>
  );
}

自定义样式

通过 classNames 和 styles 自定义样式。

import { Radio, RadioGroup } from "@/components/composed/radio/radio";

export function RadioCustomStyle() {
  return (
    <RadioGroup defaultValue="option1">
      <Radio
        value="option1"
        id="custom-1"
        classNames={{
          root: "h-5 w-5 border-2",
          label: "text-base font-semibold",
        }}
        styles={{
          wrapper: { padding: "8px" },
        }}
      >
        自定义样式 1
      </Radio>
      <Radio
        value="option2"
        id="custom-2"
        classNames={{
          wrapper: "bg-gray-50 p-3 rounded",
        }}
      >
        自定义样式 2
      </Radio>
    </RadioGroup>
  );
}

API

Radio

参数说明类型默认值
checked指定当前是否选中booleanfalse
defaultChecked初始是否选中booleanfalse
disabled禁用 Radiobooleanfalse
classNames用于自定义组件内部各语义化结构的 class,支持对象或函数RadioClassNames | (info: { props }) => RadioClassNames-
styles用于自定义组件内部各语义化结构的行内 style,支持对象或函数RadioStyles | (info: { props }) => RadioStyles-
value根据 value 进行比较,判断是否选中any-
idRadio 的 id 属性string-
nameRadio 的 name 属性string-
childrenRadio 标签内容React.ReactNode-

RadioGroup

参数说明类型默认值
block将 RadioGroup 宽度调整为其父宽度的选项booleanfalse
buttonStyleRadioButton 的风格样式,目前有描边和填色两种风格"outline" | "solid""outline"
classNames用于自定义组件内部各语义化结构的 class,支持对象或函数RadioClassNames | (info: { props }) => RadioClassNames-
defaultValue默认选中的值any-
disabled禁选所有子单选器booleanfalse
nameRadioGroup 下所有 input[type="radio"] 的 name 属性string-
options以配置形式设置子元素(string | number | RadioOptionType)[][]
optionType用于设置 Radio options 类型"default" | "button""default"
orientation排列方向"horizontal" | "vertical""horizontal"
size大小,只对按钮样式生效"large" | "middle" | "small""middle"
styles用于自定义组件内部各语义化结构的行内 style,支持对象或函数RadioStyles | (info: { props }) => RadioStyles-
value用于设置当前选中的值any-
vertical值为 true,Radio Group 为垂直方向booleanfalse
onChange选项变化时的回调函数(value: any) => void-
children子元素React.ReactNode-
className组容器的类名string-
style组容器的内联样式React.CSSProperties-
title组的标题string-

RadioClassNames

属性说明类型
root单选框根元素的类名string
indicator选中指示器的类名string
label标签的类名string
wrapper外层包装器的类名string

RadioStyles

属性说明类型
root单选框根元素的样式React.CSSProperties
indicator选中指示器的样式React.CSSProperties
label标签的样式React.CSSProperties
wrapper外层包装器的样式React.CSSProperties

RadioOptionType

属性说明类型默认值
label用于作为 Radio 选项展示的文本React.ReactNode-
value关联 Radio 选项的值string | number | boolean-
style应用到 Radio 选项的 styleReact.CSSProperties-
classNameRadio 选项的类名string-
disabled指定 Radio 选项是否要禁用booleanfalse
title添加 Title 属性值string-
id添加 Radio Id 属性值string-
required指定 Radio 选项是否必填booleanfalse

使用场景

表单选择

在表单中收集用户的单选输入。

<RadioGroup name="gender" defaultValue="male">
  <Radio value="male" id="male">男</Radio>
  <Radio value="female" id="female">女</Radio>
  <Radio value="other" id="other">其他</Radio>
</RadioGroup>

配置选项

用于设置应用的配置选项。

<RadioGroup
  title="通知方式"
  defaultValue="email"
  orientation="vertical"
>
  <Radio value="email" id="email">邮件通知</Radio>
  <Radio value="sms" id="sms">短信通知</Radio>
  <Radio value="both" id="both">邮件和短信</Radio>
</RadioGroup>

问卷调查

在问卷中收集单选答案。

<RadioGroup
  title="您对我们的服务满意吗?"
  defaultValue="satisfied"
  orientation="vertical"
  options={[
    { label: "非常满意", value: "very-satisfied" },
    { label: "满意", value: "satisfied" },
    { label: "一般", value: "neutral" },
    { label: "不满意", value: "unsatisfied" },
  ]}
/>

最佳实践

选项数量

  • 2-5个选项:最适合使用 Radio
  • 超过5个选项:考虑使用 Select 下拉选择

标签文本

  • 使用清晰、简洁的标签文本
  • 避免使用过长的描述性文本
  • 保持选项之间的一致性

默认选中

  • 为常用场景提供合理的默认值
  • 对于必填项,建议设置默认选中值
  • 避免在没有明确偏好时强制默认选中

布局方向

  • 水平布局:适合选项较少且文本简短的情况
  • 垂直布局:适合选项较多或文本较长的情况

无障碍

  • 始终提供有意义的 label 文本
  • 使用 fieldset 和 legend 对相关选项进行分组
  • 确保键盘可以正常导航和选择

扩展示例

带描述的选项

<RadioGroup defaultValue="plan1" orientation="vertical">
  <Radio value="plan1" id="plan1">
    <div>
      <div className="font-medium">基础版</div>
      <div className="text-sm text-gray-500">适合个人用户</div>
    </div>
  </Radio>
  <Radio value="plan2" id="plan2">
    <div>
      <div className="font-medium">专业版</div>
      <div className="text-sm text-gray-500">适合小团队</div>
    </div>
  </Radio>
</RadioGroup>

卡片样式

<RadioGroup defaultValue="option1">
  {["选项 1", "选项 2", "选项 3"].map((label, index) => (
    <Radio
      key={index}
      value={`option${index + 1}`}
      id={`card-${index + 1}`}
      classNames={{
        wrapper: "border rounded-lg p-4 hover:bg-gray-50"
      }}
    >
      {label}
    </Radio>
  ))}
</RadioGroup>

带图标的选项

import { Mail, MessageSquare, Phone } from "lucide-react";

<RadioGroup defaultValue="email">
  <Radio value="email" id="email">
    <div className="flex items-center gap-2">
      <Mail className="h-4 w-4" />
      <span>邮件</span>
    </div>
  </Radio>
  <Radio value="message" id="message">
    <div className="flex items-center gap-2">
      <MessageSquare className="h-4 w-4" />
      <span>消息</span>
    </div>
  </Radio>
  <Radio value="phone" id="phone">
    <div className="flex items-center gap-2">
      <Phone className="h-4 w-4" />
      <span>电话</span>
    </div>
  </Radio>
</RadioGroup>