import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows a basic controlled radio group */
export function RadioBasicDemo() {
const [value, setValue] = useState("email");
return (
<Radio.Group
legend="Notification preference"
value={value}
onValueChange={setValue}
>
<Radio.Item label="Email" value="email" />
<Radio.Item label="SMS" value="sms" />
<Radio.Item label="Push notification" value="push" />
</Radio.Group>
);
}安装
桶式导出
import { Radio } from "@cloudflare/kumo";细粒度导入
import { Radio } from "@cloudflare/kumo/components/radio";用法
import { Radio } from "@cloudflare/kumo";
export default function Example() {
return (
<Radio.Group legend="Choose an option" defaultValue="a">
<Radio.Item label="Option A" value="a" />
<Radio.Item label="Option B" value="b" />
</Radio.Group>
);
}示例
默认布局(垂直)
单选组默认垂直排列。每个单选按钮的标签 显示在右侧。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows the default vertical radio group layout */
export function RadioDefaultDemo() {
const [value, setValue] = useState("personal");
return (
<Radio.Group legend="Account type" value={value} onValueChange={setValue}>
<Radio.Item label="Personal" value="personal" />
<Radio.Item label="Business" value="business" />
<Radio.Item label="Enterprise" value="enterprise" />
</Radio.Group>
);
}水平布局
使用 orientation="horizontal" 实现内联布局。空间不足时
项目会自动换行。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows a horizontal radio group layout */
export function RadioHorizontalDemo() {
const [value, setValue] = useState("md");
return (
<Radio.Group
legend="Size"
orientation="horizontal"
value={value}
onValueChange={setValue}
>
<Radio.Item label="Small" value="sm" />
<Radio.Item label="Medium" value="md" />
<Radio.Item label="Large" value="lg" />
</Radio.Group>
);
}Segmented
对行内、紧凑的选项使用 appearance="segmented"。选项不会换行,
请据此进行设计。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
export function RadioSegmentedDemo() {
const [duration, setDuration] = useState("1");
return (
<Radio.Group
appearance="segmented"
legend="Duration preset"
value={duration}
onValueChange={setDuration}
>
<Radio.Item label="1h" value="1" />
<Radio.Item label="12h" value="12" />
<Radio.Item label="24h" value="24" />
</Radio.Group>
);
}什么时候应该使用分段样式?
仅当选项组较小、标签较短且控件适合单行显示时, 才使用分段样式。标签较长、选项带描述、选项数量多或布局较窄时, 请使用默认样式。
当选择会切换所显示的内容面板时,请改用 Tabs 组件。
默认与分段
外观仅改变呈现方式。两个示例都表示相同的互斥值。
import { Radio } from "@cloudflare/kumo";
export function RadioAppearanceComparisonDemo() {
return (
<div className="flex flex-wrap gap-8">
<Radio.Group
legend="Default appearance"
orientation="horizontal"
defaultValue="1"
>
<Radio.Item label="1h" value="1" />
<Radio.Item label="12h" value="12" />
<Radio.Item label="24h" value="24" />
</Radio.Group>
<Radio.Group
appearance="segmented"
legend="Segmented appearance"
defaultValue="1"
>
<Radio.Item label="1h" value="1" />
<Radio.Item label="12h" value="12" />
<Radio.Item label="24h" value="24" />
</Radio.Group>
</div>
);
}禁用的分段控件
分段组支持禁用整个组或禁用单个选项。
import { Radio } from "@cloudflare/kumo";
export function RadioSegmentedDisabledDemo() {
return (
<div className="flex flex-wrap gap-8">
<Radio.Group
appearance="segmented"
legend="Disabled group"
defaultValue="12"
disabled
>
<Radio.Item label="1h" value="1" />
<Radio.Item label="12h" value="12" />
<Radio.Item label="24h" value="24" />
</Radio.Group>
<Radio.Group
appearance="segmented"
legend="Disabled item"
defaultValue="1"
>
<Radio.Item label="1h" value="1" />
<Radio.Item label="12h" value="12" disabled />
<Radio.Item label="24h" value="24" />
</Radio.Group>
</div>
);
}带自定义图例的分段
Radio.Legend 保留在分段表面之外,并作为组的
无障碍名称。
import { Radio } from "@cloudflare/kumo";
export function RadioSegmentedLegendDemo() {
return (
<Radio.Group appearance="segmented" defaultValue="automatic">
<Radio.Legend className="text-sm font-normal text-kumo-subtle">
Configuration mode
</Radio.Legend>
<Radio.Item label="Automatic" value="automatic" />
<Radio.Item label="Manual" value="manual" />
</Radio.Group>
);
}长标签与窄布局
分段控件保持其固有宽度,其项目不会换行、收缩或截断。 当翻译后的标签或较窄的容器使选项组无法在一行内放下时, 请选择默认样式。
import { Radio } from "@cloudflare/kumo";
export function RadioSegmentedLongLabelsDemo() {
return (
<div className="w-48 max-w-full">
<Radio.Group
appearance="segmented"
legend="Processing preference"
defaultValue="automatic"
>
<Radio.Item label="Automatic processing" value="automatic" />
<Radio.Item label="Manual configuration" value="manual" />
</Radio.Group>
</div>
);
}带描述
使用 description 属性在单选项目下方添加辅助说明文字。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows a radio group with helper description text */
export function RadioDescriptionDemo() {
const [value, setValue] = useState("standard");
return (
<Radio.Group
legend="Shipping method"
description="Choose how you'd like to receive your order"
value={value}
onValueChange={setValue}
>
<Radio.Item label="Standard (5-7 days)" value="standard" />
<Radio.Item label="Express (2-3 days)" value="express" />
<Radio.Item label="Overnight" value="overnight" />
</Radio.Group>
);
}控件位置
使用 controlPosition="end" 将标签放在单选按钮左侧。
import { Radio } from "@cloudflare/kumo";
/** Shows radio group with labels positioned before the radio control */
export function RadioControlPositionDemo() {
return (
<Radio.Group legend="Preferences" controlPosition="end" defaultValue="a">
<Radio.Item label="Label before radio" value="a" />
<Radio.Item label="Another option" value="b" />
</Radio.Group>
);
}卡片式单选
在组上使用 appearance="card",可将每个选项显示为单个卡片中可通过分隔线
分隔选择的行。再配合每个项目的 description 属性,可获得更丰富的内容。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows radio card appearance with Cloudflare plan options */
export function RadioCardDemo() {
const [value, setValue] = useState("free");
return (
<Radio.Group
legend="Choose a plan"
appearance="card"
value={value}
onValueChange={setValue}
>
<Radio.Item
label="Free"
description="For personal or hobby projects that aren't business-critical."
value="free"
/>
<Radio.Item
label="Pro"
description="For professional websites that aren't business-critical."
value="pro"
/>
<Radio.Item
label="Business"
description="For small businesses operating online."
value="business"
/>
<Radio.Item
label="Contract"
description="For mission-critical applications that are core to your business."
value="contract"
/>
</Radio.Group>
);
}卡片式单选(控件在左侧)
在卡片式单选组上使用 controlPosition="start",可将单选控件放在标签
和描述的左侧。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows radio card appearance with the control positioned on the left via controlPosition="start" */
export function RadioCardControlStartDemo() {
const [value, setValue] = useState("free");
return (
<Radio.Group
legend="Choose a plan"
appearance="card"
controlPosition="start"
value={value}
onValueChange={setValue}
>
<Radio.Item
label="Free"
description="For personal or hobby projects that aren't business-critical."
value="free"
/>
<Radio.Item
label="Pro"
description="For professional websites that aren't business-critical."
value="pro"
/>
</Radio.Group>
);
}富标签内容
Radio.Item 的 label 属性接受 React 节点,因此可以在文字旁边嵌入图标、
徽标或其他标记。
import { useState } from "react";
import { Badge, Radio } from "@cloudflare/kumo";
/** Shows Radio.Item labels with rich ReactNode content (icons, badges, or additional markup) */
export function RadioRichLabelDemo() {
const [value, setValue] = useState("pro");
return (
<Radio.Group
legend="Choose a plan"
appearance="card"
value={value}
onValueChange={setValue}
>
<Radio.Item
label={
<span className="flex items-center gap-2">
Free
<Badge variant="neutral">$0</Badge>
</span>
}
description="For personal or hobby projects."
value="free"
/>
<Radio.Item
label={
<span className="flex items-center gap-2">
Pro
<Badge variant="primary">Popular</Badge>
</span>
}
description="For professional websites."
value="pro"
/>
</Radio.Group>
);
}卡片式单选(水平)
将 appearance="card" 与 orientation="horizontal" 结合,
可将选项在同一个卡片内排列成两列。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows radio card appearance in horizontal layout */
export function RadioCardHorizontalDemo() {
const [value, setValue] = useState("free");
return (
<Radio.Group
legend="Choose a plan"
appearance="card"
orientation="horizontal"
value={value}
onValueChange={setValue}
>
<Radio.Item
label="Free"
description="For personal or hobby projects that aren't business-critical."
value="free"
/>
<Radio.Item
label="Pro"
description="For professional websites that aren't business-critical."
value="pro"
/>
<Radio.Item
label="Business"
description="For small businesses operating online."
value="business"
/>
<Radio.Item
label="Contract"
description="For mission-critical applications that are core to your business."
value="contract"
/>
</Radio.Group>
);
}错误状态
使用 error 属性在组级别显示校验错误。
import { Radio } from "@cloudflare/kumo";
/** Shows error state for both default and card radio groups */
export function RadioErrorDemo() {
return (
<div className="grid grid-cols-2 gap-6">
<Radio.Group
legend="Payment method"
error="Please select a payment method to continue"
>
<Radio.Item label="Credit Card" value="card" variant="error" />
<Radio.Item label="PayPal" value="paypal" variant="error" />
</Radio.Group>
<Radio.Group
legend="Payment method"
appearance="card"
error="Please select a payment method to continue"
>
<Radio.Item
label="Credit Card"
description="Pay with Visa, Mastercard, American Express, or Elo."
value="card"
variant="error"
/>
<Radio.Item
label="PayPal"
description="Pay with your PayPal account."
value="paypal"
variant="error"
/>
</Radio.Group>
</div>
);
}禁用状态
可以禁用整个组或禁用一个或多个项目。
import { Radio } from "@cloudflare/kumo";
/** Shows disabled state for both default and card radio groups */
export function RadioDisabledDemo() {
return (
<div className="grid grid-cols-2 gap-6">
<Radio.Group legend="Disabled group" disabled defaultValue="a">
<Radio.Item label="Option A" value="a" />
<Radio.Item label="Option B" value="b" />
</Radio.Group>
<Radio.Group legend="Individual disabled" defaultValue="available">
<Radio.Item label="Available" value="available" />
<Radio.Item label="Unavailable" value="unavailable" disabled />
</Radio.Group>
<Radio.Group
legend="Disabled card group"
appearance="card"
disabled
defaultValue="a"
>
<Radio.Item
label="Option A"
description="This option is disabled."
value="a"
/>
<Radio.Item
label="Option B"
description="This option is disabled."
value="b"
/>
</Radio.Group>
<Radio.Group
legend="Individual disabled card"
appearance="card"
defaultValue="available"
>
<Radio.Item
label="Available"
description="This option can be selected."
value="available"
/>
<Radio.Item
label="Unavailable"
description="This option is not available."
value="unavailable"
disabled
/>
</Radio.Group>
</div>
);
}视觉隐藏的图例
使用带 className="sr-only" 的 Radio.Legend,可以在视觉上隐藏图例的同时
保持其为屏幕阅读器可读。当单选组已由父级 Field 或标题提供了标签,
再显示图例会形成冗余标签时,
这一用法很有用。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows Radio.Legend with sr-only to visually hide the legend while keeping it accessible, useful when a parent Field already provides a visible label */
export function RadioLegendSrOnlyDemo() {
const [value, setValue] = useState("all");
return (
<Radio.Group defaultValue="all" value={value} onValueChange={setValue}>
<Radio.Legend className="sr-only">Paths</Radio.Legend>
<Radio.Item label="Allow all paths" value="all" />
<Radio.Item label="Restrict to specific paths" value="specific" />
</Radio.Group>
);
}自定义图例样式
Radio.Legend 接受 className,可完全控制图例的呈现方式。当你需要
自定义排版、颜色或布局时,请用它替代
legend 字符串属性。
import { useState } from "react";
import { Radio } from "@cloudflare/kumo";
/** Shows Radio.Legend with custom styling for full control over legend presentation */
export function RadioLegendCustomDemo() {
const [value, setValue] = useState("email");
return (
<Radio.Group value={value} onValueChange={setValue}>
<Radio.Legend className="text-sm font-normal text-kumo-subtle">
Notification preference
</Radio.Legend>
<Radio.Item label="Email" value="email" />
<Radio.Item label="SMS" value="sms" />
<Radio.Item label="Push notification" value="push" />
</Radio.Group>
);
}类型化值
Radio.Group 和 Radio.Item 支持泛型类型。传入类型参数(例如 <number> 或枚举)
可以约束两个子组件上 value 的类型,以及 Radio.Group 上 defaultValue 和
onValueChange 的类型。省略时默认为
string。
enum ThemeType {
dark = "dark",
light = "light",
system = "system",
}
export function RadioTypedValueDemo() {
const [pageSize, setPageSize] = useState<number>(10);
const [theme, setTheme] = useState<ThemeType>(ThemeType.system);
return (
<div className="grid grid-cols-2 gap-6">
<Radio.Group<number>
legend="Items per page"
value={pageSize}
onValueChange={setPageSize}
>
<Radio.Item<number> label="10" value={10} />
<Radio.Item<number> label="25" value={25} />
<Radio.Item<number> label="50" value={50} />
</Radio.Group>
<Radio.Group<ThemeType>
legend="Theme"
value={theme}
onValueChange={setTheme}
>
<Radio.Item<ThemeType> label="Light" value={ThemeType.light} />
<Radio.Item<ThemeType> label="Dark" value={ThemeType.dark} />
<Radio.Item<ThemeType> label="System" value={ThemeType.system} />
</Radio.Group>
</div>
);
}API 参考
Radio.Group
单选按钮的容器,支持图例、描述和错误提示。
| Prop | Type | Default | Description |
|---|---|---|---|
| legend | string | - | 分组的图例文本(无障碍必需)。若需更精细地控制图例样式,请省略此属性,改用 |
| children* | ReactNode | - | 子 |
| orientation | "vertical" | "horizontal" | - | 单选按钮项的布局方向。 |
| appearance | KumoRadioAppearance | - | 应用于所有
|
| error | string | - | 组的错误消息 |
| description | ReactNode | - | 组的帮助文本 |
| value | T | - | 所选单选项目的受控值。 |
| disabled | boolean | - | 是否禁用组中的所有单选按钮 |
| controlPosition | RadioControlPosition | - | 单选控件相对于标签的位置:"start" 将单选按钮放在标签之前,"end" 将标签放在单选按钮之前。默认外观下默认值为 "start",卡片外观下默认值为 "end"。 |
| name | string | - | 单选按钮组的表单提交名称 |
| className | string | - | 额外的 CSS 类 |
| defaultValue | T | - | 非受控状态下所选单选项目的初始值。 |
| onValueChange | (value: T, eventDetails: RadioGroupChangeEventDetails) => void | - | 当所选值变化时触发的回调。第二个参数携带有关交互的原生事件详情。 |
Radio.Legend
可组合的 Radio.Group 图例子组件。接受 className 以完全控制样式
(例如 className="sr-only" 实现视觉隐藏)。当你需要自定义图例样式时,
用它替代 legend 字符串属性。
| Prop | Type | Default | Description |
|---|---|---|---|
| children* | ReactNode | - | 图例内容 |
| className | string | - | 额外的 CSS 类(例如用 "sr-only" 视觉隐藏图例) |
Radio.Item
Radio.Group 中的单个单选按钮。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.