表单组件
RadioGroup 单选按钮组
单选按钮组,允许用户从一组互斥选项中选择一项。基于 Base UI Radio 构建,完整支持键盘导航。
安装
bash
npx polyui add radio-group基础用法
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupUsage() {
const frameworks = ["React", "Vue", "Svelte", "Solid"]
return (
<RadioGroup defaultValue="react">
{frameworks.map((fw) => (
<div key={fw} className="flex items-center gap-2">
<RadioGroupItem value={fw.toLowerCase()} id={`usage-${fw}`} />
<Label htmlFor={`usage-${fw}`}>{fw}</Label>
</div>
))}
</RadioGroup>
)
}禁用
Entire group disabled
Single item disabled
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupDisabled() {
return (
<div className="flex flex-col gap-6">
<div>
<p className="text-muted-foreground mb-2 text-xs">Entire group disabled</p>
<RadioGroup disabled defaultValue="react">
<div className="flex items-center gap-2">
<RadioGroupItem value="react" id="dis-r1" />
<Label htmlFor="dis-r1">React</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="vue" id="dis-r2" />
<Label htmlFor="dis-r2">Vue</Label>
</div>
</RadioGroup>
</div>
<div>
<p className="text-muted-foreground mb-2 text-xs">Single item disabled</p>
<RadioGroup defaultValue="react">
<div className="flex items-center gap-2">
<RadioGroupItem value="react" id="dis-r3" />
<Label htmlFor="dis-r3">React</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="vue" id="dis-r4" disabled />
<Label htmlFor="dis-r4">Vue</Label>
</div>
</RadioGroup>
</div>
</div>
)
}默认
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupDefault() {
return (
<RadioGroup defaultValue="higher-secondary">
<div className="flex items-center gap-2">
<RadioGroupItem value="higher-secondary" id="higher-secondary" />
<Label htmlFor="higher-secondary">Higher Secondary</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="graduation" id="graduation" />
<Label htmlFor="graduation">Graduation</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="post-graduation" id="post-graduation" />
<Label htmlFor="post-graduation">Post Graduation</Label>
</div>
</RadioGroup>
)
}水平
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupHorizontal() {
return (
<RadioGroup defaultValue="beginner" className="flex items-center gap-4">
<div className="flex items-center gap-2">
<RadioGroupItem value="beginner" id="beginner" />
<Label htmlFor="beginner">Beginner</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="intermediate" id="intermediate" />
<Label htmlFor="intermediate">Intermediate</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="advanced" id="advanced" />
<Label htmlFor="advanced">Advanced</Label>
</div>
</RadioGroup>
)
}颜色
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupColors() {
return (
<RadioGroup defaultValue="destructive" className="flex items-center gap-4">
<div className="flex items-center gap-2">
<RadioGroupItem
value="destructive"
id="color-destructive"
className="border-destructive text-destructive focus-visible:ring-destructive/20 focus-visible:border-destructive dark:focus-visible:ring-destructive/40 data-checked:border-destructive data-checked:bg-destructive"
/>
<Label htmlFor="color-destructive">Destructive</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
value="success"
id="color-success"
className="border-green-600 text-green-600 focus-visible:border-green-600 focus-visible:ring-green-600/20 dark:border-green-400 dark:text-green-400 dark:focus-visible:border-green-400 dark:focus-visible:ring-green-400/40 data-checked:border-green-600 data-checked:bg-green-600 dark:data-checked:border-green-400 dark:data-checked:bg-green-400"
/>
<Label htmlFor="color-success">Success</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
value="info"
id="color-info"
className="border-sky-600 text-sky-600 focus-visible:border-sky-600 focus-visible:ring-sky-600/20 dark:border-sky-400 dark:text-sky-400 dark:focus-visible:border-sky-400 dark:focus-visible:ring-sky-400/40 data-checked:border-sky-600 data-checked:bg-sky-600 dark:data-checked:border-sky-400 dark:data-checked:bg-sky-400"
/>
<Label htmlFor="color-info">Info</Label>
</div>
</RadioGroup>
)
}尺寸
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupSizes() {
return (
<RadioGroup defaultValue="default" className="flex items-center gap-4">
<div className="flex items-center gap-2">
<RadioGroupItem value="default" id="size-default" />
<Label htmlFor="size-default">Default</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="medium" id="size-medium" className="size-5 [&_svg]:size-3" />
<Label htmlFor="size-medium">Medium</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="large" id="size-large" className="size-6 [&_svg]:size-3.5" />
<Label htmlFor="size-large">Large</Label>
</div>
</RadioGroup>
)
}虚线
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupDashed() {
return (
<RadioGroup defaultValue="standard">
<div className="flex items-center gap-2">
<RadioGroupItem
value="standard"
id="standard"
className="border-primary focus-visible:border-primary border-dashed"
/>
<Label htmlFor="standard">Standard Shipping</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
value="express"
id="express"
className="border-primary focus-visible:border-primary border-dashed"
/>
<Label htmlFor="express">Express Delivery</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
value="overnight"
id="overnight"
className="border-primary focus-visible:border-primary border-dashed"
/>
<Label htmlFor="overnight">Overnight Shipping</Label>
</div>
</RadioGroup>
)
}实色
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupSolid() {
return (
<RadioGroup defaultValue="light">
{["light", "dark", "system"].map((theme) => (
<div key={theme} className="flex items-center gap-2">
<RadioGroupItem
value={theme}
id={`theme-${theme}`}
className="text-primary-foreground data-checked:bg-primary! data-checked:border-primary"
/>
<Label htmlFor={`theme-${theme}`}>
{theme === "light" ? "Light Theme" : theme === "dark" ? "Dark Theme" : "System Default"}
</Label>
</div>
))}
</RadioGroup>
)
}带描述
Perfect for individuals and small projects.
Advanced features for growing teams.
Full-featured solution for large organizations.
tsx
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupDescription() {
const plans = [
{
value: "basic",
label: "Basic Plan",
desc: "Perfect for individuals and small projects.",
},
{
value: "pro",
label: "Pro Plan",
desc: "Advanced features for growing teams.",
},
{
value: "enterprise",
label: "Enterprise Plan",
desc: "Full-featured solution for large organizations.",
},
]
return (
<RadioGroup defaultValue="basic">
{plans.map(({ value, label, desc }) => (
<div key={value} className="flex gap-2">
<RadioGroupItem value={value} id={`plan-${value}`} />
<div className="grid flex-1 space-y-2">
<Label htmlFor={`plan-${value}`}>{label}</Label>
<p className="text-muted-foreground text-xs">{desc}</p>
</div>
</div>
))}
</RadioGroup>
)
}芯片
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
export function RadioGroupChip() {
const id = useId()
const items = [
{ value: "1", label: "Size: 6 (UK)" },
{ value: "2", label: "Size: 7 (UK)", disabled: true },
{ value: "3", label: "Size: 8 (UK)" },
{ value: "4", label: "Size: 9 (UK)" },
{ value: "5", label: "Size: 10 (UK)" },
]
return (
<fieldset className="w-full max-w-96 space-y-4">
<legend className="text-foreground text-sm leading-none font-medium">Select Shoe Size</legend>
<RadioGroup className="grid grid-cols-3 gap-2" defaultValue="1">
{items.map((item) => (
<label
key={`${id}-${item.value}`}
className="border-input has-data-checked:border-primary/80 has-focus-visible:border-ring has-focus-visible:ring-ring/50 relative flex flex-col items-center gap-3 rounded-md border px-2 py-3 text-center shadow-xs transition-[color,box-shadow] outline-none has-focus-visible:ring-[3px] has-data-disabled:cursor-not-allowed has-data-disabled:opacity-50"
>
<RadioGroupItem
id={`${id}-${item.value}`}
value={item.value}
className="sr-only after:absolute after:inset-0"
aria-label={`size-radio-${item.value}`}
disabled={item.disabled}
/>
<p className="text-foreground text-sm leading-none font-medium">{item.label}</p>
</label>
))}
</RadioGroup>
</fieldset>
)
}列表组
$39/mo
$69/mo
Custom
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
import { Badge } from "@polyui/react/badge"
export function RadioGroupListGroup() {
const id = useId()
const items = [
{ value: "1", label: "Pro", price: "$39/mo" },
{ value: "2", label: "Team", price: "$69/mo" },
{ value: "3", label: "Enterprise", price: "Custom" },
]
return (
<RadioGroup className="w-full max-w-96 gap-0 -space-y-px rounded-md shadow-xs" defaultValue="2">
{items.map((item) => (
<div
key={`${id}-${item.value}`}
className="border-input has-data-checked:border-primary/50 has-data-checked:bg-accent relative flex flex-col gap-4 border p-4 outline-none first:rounded-t-md last:rounded-b-md has-data-checked:z-10"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<RadioGroupItem
id={`${id}-${item.value}`}
value={item.value}
className="after:absolute after:inset-0"
aria-label={`plan-radio-${item.value}`}
/>
<Label className="inline-flex items-center gap-1.5" htmlFor={`${id}-${item.value}`}>
{item.label}
{item.value === "2" && <Badge className="rounded-sm px-1.5 py-px text-xs">Best Seller</Badge>}
</Label>
</div>
<div className="text-muted-foreground text-xs">{item.price}</div>
</div>
</div>
))}
</RadioGroup>
)
}拆分列表组
$39/mo
$69/mo
Custom
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
import { Badge } from "@polyui/react/badge"
export function RadioGroupSplitListGroup() {
const id = useId()
const items = [
{ value: "1", label: "Pro", price: "$39/mo" },
{ value: "2", label: "Team", price: "$69/mo" },
{ value: "3", label: "Enterprise", price: "Custom" },
]
return (
<RadioGroup className="w-full max-w-96 gap-0 space-y-2 rounded-md *:rounded-full" defaultValue="2">
{items.map((item) => (
<div
key={`${id}-${item.value}`}
className="border-input has-data-checked:bg-primary has-data-checked:text-primary-foreground relative flex flex-col gap-4 border p-4 outline-none has-data-checked:z-10"
>
<div className="group flex items-center justify-between">
<div className="flex items-center gap-2">
<RadioGroupItem
id={`${id}-${item.value}`}
value={item.value}
aria-label={`plan-radio-${item.value}`}
className="text-primary bg-accent data-checked:bg-primary-foreground! data-checked:border-primary-foreground after:absolute after:inset-0"
/>
<Label className="inline-flex items-center gap-1.5" htmlFor={`${id}-${item.value}`}>
{item.label}
{item.value === "2" && (
<Badge
variant="outline"
className="rounded-sm border-green-500 bg-green-500/10 px-1.5 py-px text-xs text-green-500"
>
Best Seller
</Badge>
)}
</Label>
</div>
<div className="group-has-checked:text-primary-foreground text-xs">{item.price}</div>
</div>
</div>
))}
</RadioGroup>
)
}卡片单选
Essential features for personal use.
Advanced features for power users.
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupCardRadio() {
const id = useId()
const cards = [
{
value: "1",
label: "Basic",
price: "Free",
desc: "Essential features for personal use.",
},
{
value: "2",
label: "Premium",
price: "$5.00",
desc: "Advanced features for power users.",
},
]
return (
<RadioGroup className="w-full max-w-96 gap-2" defaultValue="1">
{cards.map((item) => (
<div
key={item.value}
className="border-input has-data-checked:border-primary/50 relative flex w-full items-center gap-2 rounded-md border p-4 shadow-xs outline-none"
>
<RadioGroupItem
value={item.value}
id={`${id}-${item.value}`}
aria-label={`plan-radio-${item.label.toLowerCase()}`}
className="size-5 after:absolute after:inset-0 [&_svg]:size-3"
/>
<div className="grid grow gap-2">
<Label htmlFor={`${id}-${item.value}`} className="justify-between">
{item.label}{" "}
<span className="text-muted-foreground text-xs leading-[inherit] font-normal">{item.price}</span>
</Label>
<p className="text-muted-foreground text-xs">{item.desc}</p>
</div>
</div>
))}
</RadioGroup>
)
}带边框卡片单选
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
export function RadioGroupCardRadioWithBorder() {
const id = useId()
const cards = [
{
value: "1",
label: "Basic",
price: "Free",
desc: "Essential features for personal use.",
},
{
value: "2",
label: "Premium",
price: "$5.00",
desc: "Advanced features for power users.",
},
]
return (
<RadioGroup className="w-full max-w-96 gap-2" defaultValue="1">
{cards.map((item) => (
<div
key={item.value}
className="border-input has-data-checked:border-primary/50 has-focus-visible:border-ring has-focus-visible:ring-ring/50 relative w-full rounded-md border p-3 shadow-xs transition-[color,box-shadow] outline-none has-focus-visible:ring-[3px]"
>
<RadioGroupItem
value={item.value}
id={`${id}-${item.value}`}
className="sr-only"
aria-label={`plan-radio-${item.label.toLowerCase()}`}
/>
<Label
htmlFor={`${id}-${item.value}`}
className="text-foreground flex flex-col items-start after:absolute after:inset-0"
>
<div className="flex w-full items-center justify-between">
<span>{item.label}</span>
<span className="text-muted-foreground text-xs leading-[inherit] font-normal">{item.price}</span>
</div>
<p className="text-muted-foreground text-xs">{item.desc}</p>
</Label>
</div>
))}
</RadioGroup>
)
}垂直卡片
Essential features for personal use.
Advanced features for power users.
tsx
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "@polyui/react/radio-group"
import { Label } from "@polyui/react/label"
import { UserIcon, CrownIcon } from "lucide-react"
export function RadioGroupCardVertical() {
const id = useId()
const plans = [
{
value: "1",
label: "Basic",
desc: "Essential features for personal use.",
Icon: UserIcon,
},
{
value: "2",
label: "Premium",
desc: "Advanced features for power users.",
Icon: CrownIcon,
},
]
return (
<RadioGroup className="w-full max-w-96 justify-items-center sm:grid-cols-2" defaultValue="1">
{plans.map(({ value, label, desc, Icon }) => (
<div
key={value}
className="border-input has-data-checked:border-primary/50 relative flex w-full max-w-50 flex-col items-center gap-3 rounded-md border p-4 shadow-xs outline-none"
>
<RadioGroupItem
value={value}
id={`${id}-${value}`}
className="order-1 size-5 after:absolute after:inset-0 [&_svg]:size-3"
aria-label={`plan-radio-${label.toLowerCase()}`}
/>
<div className="grid grow justify-items-center gap-2">
<Icon />
<Label htmlFor={`${id}-${value}`} className="justify-center">
{label}
</Label>
<p className="text-muted-foreground text-center text-xs">{desc}</p>
</div>
</div>
))}
</RadioGroup>
)
}Props — RadioGroup
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 受控模式下当前选中的值 |
defaultValue | string | — | 非受控模式下的默认选中值 |
onValueChange | (value: string) => void | — | 选中值变化时的回调 |
disabled | boolean | false | 是否禁用整个单选组 |
className | string | — | 自定义样式类名 |
Props — RadioGroupItem
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 该选项的值,必填 |
disabled | boolean | false | 是否禁用该选项 |
className | string | — | 自定义样式类名 |