'use client'
import { useState } from 'react'
import { RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'wish', label: '想看' },
{ value: 'doing', label: '在看' },
{ value: 'done', label: '看过' },
]
export default function Demo() {
const [status, setStatus] = useState<string | number | null | undefined>('doing')
return (
<RadioGroup value={status} onValueChange={setStatus} options={options} aria-label="收藏状态" />
)
}
tsx
用法
import { RadioGroup } from '@hina-ui/react'
ts
单选框组按 options 渲染一列单选框,value / onValueChange 绑定选中的值,选项的类型见 Select。未声明的属性都会传给根元素,应当用 aria-label 或者 aria-labelledby 给整组命名。单选框只以组的形式提供,没有单独的 Radio。
当前值:system
'use client'
import { useState } from 'react'
import { RadioGroup, Stack, Text } from '@hina-ui/react'
const options = [
{ value: 'light', label: '浅色' },
{ value: 'dark', label: '深色' },
{ value: 'system', label: '跟随系统' },
]
export default function Demo() {
const [theme, setTheme] = useState<string | number | null | undefined>('system')
return (
<Stack gap="sm">
<RadioGroup value={theme} onValueChange={setTheme} options={options} aria-label="主题" />
<Text size="sm" tone="muted">
当前值:{theme}
</Text>
</Stack>
)
}
tsx
示例
横排
orientation 为 horizontal 时选项横向排列,放不下时折行。
'use client'
import { useState } from 'react'
import { RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'updated', label: '最近更新' },
{ value: 'rating', label: '评分' },
{ value: 'title', label: '标题' },
]
export default function Demo() {
const [sort, setSort] = useState<string | number | null | undefined>('updated')
return (
<RadioGroup
value={sort}
onValueChange={setSort}
options={options}
orientation="horizontal"
aria-label="排序方式"
/>
)
}
tsx
描述
选项的 description 显示在文字下方。
'use client'
import { useState } from 'react'
import { RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'public', label: '公开', description: '所有人都能看到这条动态' },
{ value: 'friends', label: '仅好友', description: '只有互相关注的用户能看到' },
{ value: 'private', label: '仅自己', description: '只有你自己能看到' },
]
export default function Demo() {
const [visibility, setVisibility] = useState<string | number | null | undefined>('friends')
return (
<RadioGroup
value={visibility}
onValueChange={setVisibility}
options={options}
aria-label="可见范围"
/>
)
}
tsx
设置行
使用 controlPlacement="end" 将控件放到文案末端,配合 block 撑满容器宽度。说明始终位于标题下方;start 和 end 会跟随文字方向。启用 block 后,纵向选项各自撑满一行,横向选项等分行宽。orientation 仍表示多个选项的排列方向。
RTL 布局请向组传入 dir="rtl",或通过 ConfigProvider 配置。
'use client'
import { useState } from 'react'
import { RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'public', label: '公开', description: '所有人都能看到这条动态' },
{ value: 'friends', label: '仅好友', description: '只有互相关注的用户能看到' },
{ value: 'private', label: '仅自己', description: '只有你自己能看到' },
]
export default function Demo() {
const [visibility, setVisibility] = useState<string | number | null | undefined>('friends')
return (
<RadioGroup
value={visibility}
onValueChange={setVisibility}
controlPlacement="end"
block
options={options}
aria-label="可见范围"
/>
)
}
tsx
尺寸
size 下发到每个单选框,圆与文字随档位变化。
import { Inline, RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'wish', label: '想看' },
{ value: 'doing', label: '在看' },
]
export default function Demo() {
return (
<Inline gap="lg" align="start">
<RadioGroup size="sm" options={options} value="wish" aria-label="小号" />
<RadioGroup size="md" options={options} value="wish" aria-label="中号" />
<RadioGroup size="lg" options={options} value="wish" aria-label="大号" />
</Inline>
)
}
tsx
状态
选项上的 disabled 只禁用该项,组上的 disabled 禁用整组;invalid 落到每个圆。
import { Inline, RadioGroup } from '@hina-ui/react'
const options = [
{ value: 'wish', label: '想看' },
{ value: 'doing', label: '在看' },
{ value: 'done', label: '看过', disabled: true },
]
export default function Demo() {
return (
<Inline gap="lg" align="start">
<RadioGroup options={options} value="wish" aria-label="含禁用项" />
<RadioGroup disabled options={options} value="wish" aria-label="整组禁用" />
<RadioGroup invalid options={options} aria-label="校验未通过" />
</Inline>
)
}
tsx
定制内容
renderOption 属性定制每一项的文字。
组件从 options 推断完整选项类型,渲染函数中的 option 保留额外字段及其类型;value / onValueChange 仍绑定 value。类型定义见 Select。
'use client'
import { useState } from 'react'
import { Monitor, Moon, Sun } from 'lucide-react'
import { Inline, RadioGroup } from '@hina-ui/react'
const icons = { light: Sun, dark: Moon, system: Monitor }
const options = [
{ value: 'light', label: '浅色' },
{ value: 'dark', label: '深色' },
{ value: 'system', label: '跟随系统' },
]
export default function Demo() {
const [theme, setTheme] = useState<string | number | null | undefined>('light')
return (
<RadioGroup
value={theme}
onValueChange={setTheme}
options={options}
aria-label="主题"
renderOption={({ option }) => {
const Icon = icons[option.value as keyof typeof icons]
return (
<Inline as="span" gap="xs" align="center">
<Icon className="text-muted size-4" />
{option.label}
</Inline>
)
}}
/>
)
}
tsx
在表单中
放进 FormField 后,标签通过 aria-labelledby 关联到整组,错误信息由字段渲染;校验规则与提交交给 Form。
'use client'
import { useState } from 'react'
import * as v from 'valibot'
import { Button, Form, FormField, RadioGroup, Text } from '@hina-ui/react'
const options = [
{ label: '公开', value: 'public', description: '所有人可见' },
{ label: '仅关注者', value: 'followers', description: '关注你的人可见' },
{ label: '私密', value: 'private', description: '只有自己可见' },
]
const schema = v.object({
visibility: v.string('请选择可见范围'),
})
export default function Demo() {
const [values, setValues] = useState({ visibility: null as string | number | null | undefined })
const [saved, setSaved] = useState('')
async function save(data: unknown) {
await new Promise(resolve => setTimeout(resolve, 600))
setSaved(JSON.stringify(data))
}
return (
<Form values={values} rules={schema} className="w-80" onSubmit={save}>
{({ submitting }) => (
<>
<FormField name="visibility" label="可见范围" required>
<RadioGroup
value={values.visibility}
onValueChange={visibility => setValues({ ...values, visibility })}
options={options}
/>
</FormField>
<Button type="submit" loading={submitting} className="self-start">
发布
</Button>
{saved && (
<Text tone="muted" size="sm">
已发布:{saved}
</Text>
)}
</>
)}
</Form>
)
}
tsx
行为
- 点击文字或者圆即选中该项,已选中的项不能再点成未选中。
- 整组只占一个 Tab 停靠点,落在已选项上;方向键在项之间移动焦点并同时选中,禁用项会被跳过,到底后回到另一端。与原生单选框一致。
无障碍
- 根元素是
role="radiogroup",通过aria-label或者aria-labelledby命名;每一项是role="radio"的按钮,带aria-checked,外层label的文字即名称。 invalid会给每个单选框设置aria-invalid。
API
Props
T extends SelectOption 从 options 推断,默认是 SelectOption。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | number | null | — | 选中的值 |
options | T[] | — | 选项,类型见 Select |
orientation | 'vertical' | 'horizontal' | 'vertical' | 排列方向 |
size | 'sm' | 'md' | 'lg' | 'md' | 每个单选框的尺寸 |
controlPlacement | 'start' | 'end' | 'start' | 控件相对于文案的位置 |
block | boolean | false | 整组撑满,横向选项等分宽度 |
disabled | boolean | false | 是否禁用整组 |
invalid | boolean | false | 是否处于校验未通过状态 |
className | string | — | 追加至根元素的类名 |
内容属性
| 属性 | 参数 | 说明 |
|---|---|---|
renderOption | { option: T } | 每一项的文字 |
回调
| 回调 | 参数 | 说明 |
|---|---|---|
onValueChange | value: string | number | 选中值变化 |