跳到主要内容
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
开始使用WebReactSwiftUIJetpack ComposeFlutterReact Native预设你自己的设计系统与 AI 代理一起使用 Tanrei品牌设计令牌设计系统与定制Theme Studio颜色字体排印图标形状层级动效交互状态行为布局无障碍文字颜色令牌字阶令牌形状令牌层级令牌动效令牌状态令牌间距与度量令牌组件按钮主操作按钮分段按钮按钮组带菜单的按钮工具栏复制按钮复选框单选按钮开关纸片(Chip)菜单滑块滑块字段日历DateTimePicker字段组单选组复选框组下拉选择框下拉选择字段菜单栏文件上传文件选择器 进阶放置区文本输入框 进阶文本字段纸片字段字段组合框 进阶组合框字段数字输入框 进阶数字字段颜色输入框 进阶颜色字段一次性验证码输入框 进阶一次性验证码字段嵌入式文本输入框 进阶聊天输入栏卡片分隔线模态层 进阶对话框列表列表行底部面板侧边面板轮播代码块表格弹出框步骤折叠面板描述列表图示应用外壳页头树视图时间线图片预览调整手柄可调整大小的框滚动区域徽标进度指示器计量条数字滚动器加载指示器消息条(Snackbar)工具提示键盘快捷键标签(Tag)行内代码提示块(Callout)头像空状态标签组(Tag group)骨架屏文件项色块跑马灯文本标签页标签页视图应用栏导航栏导航抽屉搜索链接分页上一页/下一页链接步骤条面包屑目录链接卡片FAB 菜单状态Tanrei 是如何构建的参与贡献许可与致谢
首页组件单选组

单选组

提出一个在同时全部显示出来的少量选项中恰好只有一个答案的问题——一组单选按钮,由它保存选中的值;它带有可以说明必填或选填的标签、一段描述,以及针对整组的一条错误。

content_copy以 Markdown 格式查看
类别
选择
令牌
comp.field-group
层级
组合

概览

Web
tanrei-radio-group
schedule
SwiftUI
TanreiRadioGroup
radio_button_unchecked
Jetpack Compose
TanreiRadioGroup
radio_button_unchecked
Flutter
TanreiRadioGroup
radio_button_unchecked
React Native
RadioGroup
radio_button_unchecked

生成自 docs/status/status.json, specs/radio-group.spec.json

一组单选按钮,带有名称、描述和必填标记

单选按钮从不单独使用。单选组把几个单选按钮变成一个问题:它给这一组一个名称、一行说明该怎么回答的文字、一个说明是否必须回答的词,以及回答被拒绝时的一条原因,并把答案作为一个值来保存。

组成部分是一个字段组和若干单选按钮。字段组是框架,用的是它的 radio-group 种类:标签、必填或选填标记、描述、错误、排成一列或一行的布局——以及告知辅助技术的那一个单选组。单选按钮保存值:每一个都是带有自己标签的选项,选中的是哪一个,答案就是哪一个。单选组自己什么都不绘制,也没有令牌。它在这两者之上加的,是否则每个产品都得自己写的状态:一个可读可写的值和与它对应的一个 change、方向键和为这一组保留的焦点顺序中的一个停靠点,以及作为一个控件在表单中的位置。

它放在哪里,就和那里一样宽。

在 Web 上它是 <tanrei-radio-group>。其他平台尚未实现。

用法

<tanrei-radio-group label="配送方式" name="delivery" value="standard" required required-text="必填"
                    supporting-text="订单发货前可以更改。">
  <tanrei-radio-button value="standard" label="标准配送(3–5 天)"></tanrei-radio-button>
  <tanrei-radio-button value="express" label="加急配送(次日达)"></tanrei-radio-button>
  <tanrei-radio-button value="pickup" label="到店自提"></tanrei-radio-button>
</tanrei-radio-group>

<script type="module">
  import '@tanrei/web/components/radio-group';

  const group = document.querySelector('tanrei-radio-group');
  group.addEventListener('change', (event) => {
    // event.detail.value 是新的值;group.value 已经是这个值了。
  });
</script>
import { RadioButton, RadioGroup } from '@tanrei/react';

<RadioGroup label="配送方式" value={delivery} onChange={(event) => setDelivery(event.detail.value)}
            error={refused} errorText="此地址不支持加急配送,请选择其他配送方式。">
  <RadioButton value="standard" label="标准配送(3–5 天)" />
  <RadioButton value="express" label="加急配送(次日达)" />
  <RadioButton value="pickup" label="到店自提" />
</RadioGroup>

在单选组里,单选按钮不需要 name,也不需要 checked:哪一个被选中,由组的 value 说了算。

下一层

用它的两个成员手写出来的同一组,画面是一样的,对辅助技术来说也是同一个单选组。用 kind="radio-group" 告诉字段组它的这一组是什么,各个单选按钮带同一个 name:

<tanrei-field-group kind="radio-group" label="配送方式"
                    supporting-text="订单发货前可以更改。"
                    required required-text="必填">
  <tanrei-radio-button name="delivery" value="standard" label="标准配送(3–5 天)" checked></tanrei-radio-button>
  <tanrei-radio-button name="delivery" value="express" label="加急配送(次日达)"></tanrei-radio-button>
  <tanrei-radio-button name="delivery" value="pickup" label="到店自提"></tanrei-radio-button>
</tanrei-field-group>

手写出来的同一组:种类为 radio-group 的字段组,里面是同一个 name 的单选按钮

手写的字段组和单选按钮不做的事,是保存答案。画出来的和说出来的,没有任何不同。

方面 手写 单选组
答案 由产品从单选按钮上读取 value,以及一个元素上的一个 change
设置答案 在某个单选按钮上设置 checked value = "express"
表单提交什么 同一个条目,由选中的单选按钮提交 同一个条目,由组提交
必填的一组没有回答 画出来和说出来都是必填;表单不会拒绝它 表单自己的校验会拒绝它
选中的选项被移除,或者还没有显示 答案丢失 value 保留
选中的选项被你单独禁用 什么都不提交 提交它的值
方向键、Tab 键的一个停靠点、每个单选按钮在这一组里的位置 相同,由同一个 name 的单选按钮负责 相同,由组负责

这一组要在选项之间放单选组不负责排布的东西时——比如每个选项下面一行你自己的内容——或者选项必须以插槽不允许的方式放在你自己的元素里时,就手写字段组和单选按钮。

规格

必填却未回答而处于错误状态的一组、被拒绝的回答、排成一行,以及有一个选项被禁用的一组

构成

部件 说明
1 field-group The field group, in its radio-group kind: the label with its requirement mark, the description under it, the column or the row the options stand in, and the error text below the last option — and the one radio group that assistive technology is told of. The field group itself, with its own tokens, not a drawing of it.
2 radio-button The radio buttons of the set, in the order given: the product's own, each the Radio button component with its own label, drawn from its own tokens. Which of them is selected is the group's value.

属性

属性 类型 默认值 说明
value string "" The value of the selected radio button; the empty string while none is selected. The radio group sets it when a person selects an option, and emits change; the product may also set it.
label string "" Names the set, visibly and to assistive technology: the question its controls answer ("Notify me by"). Required. — Field group
labelHidden boolean false Does not draw the label, which stays the accessible name of the group. For a set whose visible heading is elsewhere. — Field group
supportingText string "" The description: how to answer ("Choose all that apply"), drawn under the label and given to assistive technology as the group's description. — Field group
errorText string "" What is wrong with the set as a whole and how to put it right ("Choose at least one"), drawn below the controls while error is true. — Field group On the web it is also what the form's own validation says of a required group left unanswered, where the browser has no sentence of its own for it (RGP-9).
error boolean false Puts the set in the error condition: the error text is drawn, and each selection control that has an error condition of its own (a checkbox, a radio button) is drawn in it. What an answer means is the product's to validate; the group never sets this itself. One control of a valid group may still be in error by itself. — Field group The radio group is exposed as invalid, and its radio buttons are drawn as they are: a radio button has no error condition (RGP-8).
disabled boolean false Disables every control of the set and draws the frame as disabled. — Field group The value is kept, and on the web it is not submitted (RGP-7, RGP-9).
readOnly boolean false Shows the answer and keeps it: the group can be focused and its options read, its value is submitted, but the person cannot change it (RGP-20).
required boolean false What the form asks of the set: true exposes it as required and draws the required mark beside the label. It never validates: a missing answer is the product's to report with error. — Field group exposes the radio group as required, and on the web an empty value is reported to the form's own validation (RGP-9, RGP-10).
optional boolean false Draws the optional tag after the label and adds optionalText to the group's description, because assistive technology has no state for it. Ignored while required is true: a set is never both. — Field group
requiredMark auto | tag | asterisk | none "auto" How this set shows that it is required: tag is a small filled Tag with requiredText, asterisk is "*" after the label, none draws nothing and keeps the semantics. Auto takes comp.field-group.requirement.mark, which follows sys.behavior.requirement-mark, the system's one answer. Ignored unless required is true. — Field group
requiredText string "" The text of the required tag, in the product's language ("Required", "必須"). The product's: Tanrei has no word for it. Empty reads the product's words; with neither, FGR-24 says what is drawn. — Field group Not part of the radio group's description: its required state carries it (RGP-10).
optionalText string "" The text of the optional tag, in the product's language ("Optional", "任意"); also read to assistive technology as part of the description. The product's: Tanrei has no word for it. Empty reads the product's words. — Field group
orientation vertical | horizontal "vertical" vertical stacks the controls in one column, the default and the form to prefer: every label starts on one edge and is read down. horizontal lays them out in a row that wraps, for two or three short options or a row of fields. — Field group The arrow keys are the same in both (RGP-5).

事件

事件 说明
change The person selected another option; carries the new value, which the group already holds. Emitted once per selection, by the group and not by its radio buttons. Not emitted when the product sets value, or when the selected option is activated again.

条目

该组件重复排列的条目种类。测试样例按顺序列出条目;各平台以各自的惯用方式为每个种类命名。

radio-button

An option of the set: the radio button component with its own label, selected by the group's value, placed, spaced and disabled by the group, otherwise drawn and pressed as it is anywhere.

属性 类型 默认值 说明
value string "" What the option stands for, as the Radio button's value: the group's value while this option is the selected one. Unique in the group and not empty.
label string "" Its own label, as the Radio button's label.
disabled boolean false As the Radio button's disabled: one option of an enabled group may be disabled by itself, and is then skipped by the arrow keys.
规格 Web SwiftUI Jetpack Compose Flutter React Native
条目 tanrei-radio-button TanreiRadioButton TanreiRadioButton TanreiRadioButton RadioButton
value · 属性 value value value value value
label · 属性 label title label label label
disabled · 属性 disabled disabled enabled enabled disabled
备注 A tanrei-radio-button child without a name: inside a tanrei-radio-group it takes its group, its checked, its tab stop and its position from the group and submits nothing itself (RAD-25).

状态

行为

以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。

ID 需求
RGP-1 THE radio group SHALL be a field group whose kind is radio-group and whose spacing is controls, around its radio buttons: the field group itself, with the field group's own tokens, measures and sentences, and no copy of it — its rows, their order and their gaps (FGR-1), the label above the options (FGR-2), the requirement and its mark (FGR-4, FGR-5, FGR-6), the description under the label, which stays while the group is in error (FGR-7), the error text below the last option (FGR-8) and the disabled frame (FGR-14), each from the prop of the same name, which is the field group's. It SHALL have no look and no token of its own: it SHALL draw no container, edge, fill or elevation, SHALL add no padding around its rows and no distance between its radio buttons but the field group's, and SHALL leave each radio button drawn from the radio button's own tokens, at the radio button's own size.
RGP-2 THE radio group SHALL lay out its radio buttons as the field group lays out its controls: WHILE orientation is vertical in one column in the order given (FGR-3), and WHILE orientation is horizontal in a row from the start edge that wraps onto further lines and never cuts, scrolls or narrows an option below what its content needs (FGR-13). The order given is the order of reading, of the arrow keys (RGP-5) and of each option's position (RGP-10) in both orientations.
RGP-3 THE radio group SHALL hold one value and show as selected the radio button whose value equals it, and no other; WHILE value is the empty string none SHALL be selected. WHEN the product sets value THE radio group SHALL select the radio button of that value and unselect the others (RAD-3), over the radio button's own selection motion (RAD-15), without emitting change and without moving focus. WHEN the product sets checked on one of the group's radio buttons THE radio group SHALL take that radio button's value as its value, likewise without emitting change, so that the two never disagree; WHEN the product takes checked off the radio button that value selects THE radio group SHALL keep value and show that radio button selected again at once, because the empty string is the one way to say that none is selected. A radio button that comes to the group checked — with the group or after it — SHALL give the group its value only WHILE value is the empty string, the last of several such; WHILE value is not empty, value SHALL decide and the mark SHALL be overruled. IF value equals no radio button's value THEN THE radio group SHALL show none selected, SHALL keep value as it was given, and in debug builds SHALL report it; IF two radio buttons carry one value THEN the first in the order given SHALL be the one value selects; IF a radio button carries the empty string THEN it SHALL never be shown selected, and a person's activation of it SHALL change nothing and emit nothing, because its selection would say that none is selected; in debug builds THE radio group SHALL report either.
RGP-4 WHEN the person selects a radio button of the group that was not selected — by pointer, touch, Space, a click on its label, an arrow key (RGP-5) or assistive technology, as RAD-1 says of the radio button — THE radio group SHALL set value to that radio button's value and emit change exactly once, carrying the new value, with value already set when the event is received; the radio button that was selected before SHALL become unselected and emit nothing (RAD-3), and nothing the selected radio button emits SHALL reach a listener on the group as a second change. WHEN the person activates the radio button that is already selected THE radio group SHALL keep value and emit nothing (RAD-2): a person cannot clear a radio group, and a question that may go unanswered has an option that says so.
RGP-5 WHEN one of the group's radio buttons has keyboard focus and the person presses Down Arrow or Right Arrow THE radio group SHALL move focus to the next enabled radio button in the order given, wrapping from the last to the first, and select it as RGP-4 says; Up Arrow and Left Arrow SHALL do the same towards the previous one, wrapping from the first to the last; in a right-to-left layout Right Arrow SHALL move to the previous and Left Arrow to the next, and Up Arrow and Down Arrow SHALL stay as they are. The four keys SHALL do this in both orientations, and by the order given, not by the place a wrapped row gave an option. THE radio group SHALL skip a disabled radio button, SHALL do nothing WHILE the focused one is the only enabled one, and SHALL leave a key pressed with Alt, Control or Meta to the platform. This is RAD-4, kept by the group for its radio buttons.
RGP-6 THE radio group SHALL keep exactly one of its radio buttons in the focus order: the selected one WHILE it is enabled, otherwise the first enabled one in the order given; Tab and Shift+Tab SHALL enter the group at that radio button from either side and SHALL leave the group from whichever radio button has focus, never moving within it (RAD-5). THE radio group SHALL NOT be a stop itself, and a press on its label, its requirement mark, its description or its error text SHALL select nothing and move no focus into the group (FGR-12). WHEN the radio button that is the stop changes while focus is inside the group THE radio group SHALL leave focus where it is. WHILE no radio button is enabled the group SHALL have no stop. WHEN the product asks the group itself for focus, where the platform has such a call, THE radio group SHALL give focus to the radio button that is the stop.
RGP-7 WHILE disabled is true THE radio group SHALL draw its frame from the field group's disabled tokens (FGR-14), SHALL make every radio button it holds disabled as the radio button's own disabled does (RAD-6, RAD-10) — not focusable, not operable, drawn disabled, the selected one still shown selected — SHALL emit no change, SHALL keep value, and SHALL be exposed as disabled, its texts still readable by assistive technology. WHILE disabled is false THE radio group SHALL NOT enable a radio button the product disabled itself: that one stays out of the focus order and of the arrow keys (RGP-5, RGP-6) and is still shown selected WHILE value is its value.
RGP-8 THE radio group SHALL take from required and optional what the form asks of the set, as a field group does (FGR-4), and SHALL NOT set the error condition itself because an answer is missing or for any other reason: error and errorText are the product's to set. WHILE error is true THE radio group SHALL draw errorText and the error parts of its frame (FGR-8), SHALL be exposed as invalid (RGP-10), and SHALL have every radio button it holds, selected or not, drawn in the radio button's own error form (RAD-26), as a field group in error marks each control that has an error condition (FGR-8): the refusal is the set's, said by the words below it and by the group's invalid state, and shown on every option, because any of them may be the answer that puts it right. A disabled radio button SHALL be drawn as disabled, without the error color (RAD-26), and THE radio group SHALL NOT expose a radio button as invalid or describe it by errorText: the group says both once (RGP-10). WHEN error becomes true, or errorText changes while error is true, and focus is on one of the group's radio buttons THE radio group SHALL have assistive technology announce errorText once, politely, and SHALL NOT move focus (FGR-9). A change of value SHALL NOT clear error.
RGP-9 WHERE the platform has forms that submit named values (the web) THE radio group SHALL take part in its form as one control: it SHALL submit value under its own name WHILE value is not the empty string and the group is not disabled, and nothing otherwise — also WHILE the radio button of that value is one the product disabled by itself, because the value is the group's and a radio button's disabled says who may change it; SHALL return to the value it was first given WHEN the form is reset, without emitting change; SHALL be disabled WHILE the form disables the set it is in; SHALL report a missing value to the form's own validation WHILE required is true and value is the empty string, in the platform's own words for an unanswered set of radio buttons where it has them, and otherwise in errorText, else requiredText, else label — never in words of Tanrei's; and its radio buttons SHALL submit nothing of their own. Enter on a focused radio button SHALL be left to the form (RAD-1). WHERE the platform has no such forms value and change are the whole of it.
RGP-10 THE radio group SHALL be exposed to assistive technology as the field group it is, in its radio-group kind, and SHALL add no group to it (FGR-25): one radio group that contains its radio buttons — named by label, whether or not the label is drawn; required WHILE required is true; invalid WHILE error is true; disabled WHILE disabled is true; read-only WHILE readOnly is true (RGP-20); saying its orientation where the platform's radio group has one; and described by, in this order, supportingText, errorText WHILE error is true, and optionalText WHILE optional is true, each new text exposed at once WHEN it changes (FGR-11). requiredText SHALL NOT be part of the description, because the required state carries it, and the requirement tag and the asterisk SHALL be kept from assistive technology as drawn parts. Each radio button SHALL expose its role, its checked state, its position in the group and the group's size as RAD-16 says, counted in the order given with disabled options included. WHERE the platform forms a radio group with a container of its own THE radio group SHALL be that container and SHALL NOT expose a second group around it. WHILE label is empty THE radio group SHALL still render and in debug builds report that the set has no accessible name (FGR-10).
RGP-11 WHEN a radio button is added to the group, removed from it or moved, or its value or its disabled changes, while the group is shown THE radio group SHALL lay out its radio buttons again at once, without a motion; SHALL select by value again (RGP-3), so that a radio button that arrives with the group's value is shown selected and value is kept WHEN the selected one leaves; SHALL give the disabled state it holds (RGP-7) to one that came and take it back from one that left; SHALL set the tab stop (RGP-6) and each position (RGP-10) again; SHALL emit no change; and SHALL leave focus on the radio button that has it.
RGP-12 THE radio group SHALL hold radio buttons and nothing else that can be operated. IF it holds no radio button THEN THE radio group SHALL draw its rows without one and in debug builds report it; IF it holds another control, or a radio button that names a group of its own (RAD-25), THEN in debug builds THE radio group SHALL report it. THE radio group SHALL NOT restyle a radio button or change how it answers hover, focus or a press: those stay the radio button's (RAD-7, RAD-8, RAD-11 to RAD-13).
RGP-13 WHILE the text size is enlarged THE radio group SHALL grow with its texts as a field group does (FGR-16): the label, the description, the error text and each option's label wrap and never clip, the requirement tag grows with its text, a horizontal row wraps and at 200% becomes a column where one line no longer holds two options (RGP-2), and no text SHALL be lost, lie over another part or need scrolling in two directions.
RGP-14 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE radio group SHALL draw its frame as a field group does there (FGR-17) — its texts in the platform's text color, its requirement tag with an edge — and SHALL rely on no color of its own to show an error: the error is carried by the words of errorText and by the invalid state (RGP-10), and its radio buttons are drawn there as out of error (RAD-27).
RGP-15 WHERE the layout direction is right-to-left THE radio group SHALL mirror its layout as a field group does (FGR-18): the label row, the description, the options and the error text start at the right edge, the requirement mark stands at the left of the label, and a horizontal row runs from right to left, the first option at the right; a column keeps its order from top to bottom. Each radio button mirrors itself (RAD-17), and Left Arrow and Right Arrow exchange what they do (RGP-5).
RGP-16 THE radio group SHALL take its layout direction from where it is used and SHALL have no direction setting of its own, as the field group (FGR-19) and each radio button (RAD-22) do; WHEN that direction changes while the radio group is shown THE radio group SHALL be drawn for the new direction at once, without a motion of its own, keeping its value, its selection and its focus, and the arrow keys SHALL follow the new direction at the next key (RGP-5).
RGP-17 THE radio group SHALL lay out its label, its description, its error text and the text of its requirement tag as the field group does (FGR-21): each as text of its own direction, isolated from the text around it, and placed and aligned by the layout direction, not by the text's own. Each option's label is the radio button's (RAD-23).
RGP-18 WHEN requiredMark or orientation names a value the radio group does not know THE radio group SHALL do with it what the field group does (FGR-22) — render with the prop's default (auto, vertical) — and in debug builds the unknown value SHALL be reported once.
RGP-19 THE radio group SHALL have no words of its own: requiredText and optionalText are the field group's, read as FGR-24 says — from the instance's prop and, WHILE that is empty, from the product's words of the same name, and from nowhere else. IF neither gives requiredText WHILE required is true and the required mark is tag THEN THE radio group SHALL show the asterisk of FGR-6 in the tag's place, as the field group does, and SHALL keep its required state (RGP-10), which needs no word. IF neither gives optionalText WHILE optional is true THEN THE radio group SHALL show no tag and SHALL add nothing to the description for it. In debug builds the missing words SHALL be reported once.
RGP-20 WHILE readOnly is true and disabled is false THE radio group SHALL make every radio button it holds read-only as the radio button's own readOnly does (RAD-28), without changing the readOnly the product gave any of them: the person can enter the group at its stop, which stays the selected radio button (RGP-6), move focus among its enabled radio buttons with the arrow keys as RGP-5 says while selecting none, and read each one's label, checked state and position, and cannot change value by any input. THE radio group SHALL then emit no change, SHALL keep value, SHALL be exposed as read-only beside its other states (RGP-10), SHALL be submitted with its form as RGP-9 says, SHALL NOT report a missing value to the form's own validation, because the person cannot give one, and SHALL draw its frame and its radio buttons from their enabled tokens, never the disabled ones. WHILE disabled is true as well, disabled wins (RGP-7).
RGP-21 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE radio group SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on the radio group of the field group it is (RGP-10) — an id reference by the elements it names in the host's tree or a tree around it, a description after its own — except those the radio group owns (aria-label, aria-labelledby, aria-disabled, aria-required, aria-invalid, aria-errormessage, aria-readonly), which it SHALL move and not expose: its name is label and its states are its props (RGP-10, RGP-20). WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE radio group SHALL take it off that element. WHEN the product sets an aria- attribute the radio group owns THE radio group SHALL report it in debug builds.

各平台名称对照

对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-radio-group TanreiRadioGroup TanreiRadioGroup TanreiRadioGroup RadioGroup
value · 属性 value selection value groupValue value
label · 属性 label label label labelText label
labelHidden · 属性 labelHidden labelsHidden labelHidden labelHidden labelHidden
supportingText · 属性 supportingText supportingText supportingText helperText supportingText
errorText · 属性 errorText errorText errorText errorText errorText
error · 属性 error error isError isError error
disabled · 属性 disabled disabled enabled enabled disabled
readOnly · 属性 readOnly readOnly readOnly readOnly readOnly
required · 属性 required isRequired required required required
optional · 属性 optional isOptional optional optional optional
requiredMark · 属性 requiredMark requiredMark requiredMark requiredMark requiredMark
requiredText · 属性 requiredText requiredText requiredText requiredText requiredText
optionalText · 属性 optionalText optionalText optionalText optionalText optionalText
orientation · 属性 orientation orientation orientation orientation orientation
change · 事件 change selection onValueChange onChanged onValueChange
备注 <tanrei-radio-group label="Delivery" name="delivery" value="express"><tanrei-radio-button value="standard" label="Standard"></tanrei-radio-button>…</tanrei-radio-group>; class TanreiRadioGroup; in @tanrei/react, RadioGroup with value and onChange. Composed in the construction the checkbox group has: the members that hold the value are the product's own children. The shadow root holds one <tanrei-field-group part="field-group" kind="radio-group" spacing="controls"> — the member's own element, not drawing code shared with it — around a <slot>; the radio buttons stay in the product's light DOM and are assigned through the group's slot into the field group's. The eleven props that are the field group's are set on that element as they are given, and its documented parts — label, requirement-label, supporting-text, error-text — are exported under its own names (exportparts); the group writes none of its custom properties, so --tanrei-comp-field-group-* set on the host is inherited by the field group inside. The radio group that assistive technology is told of is the field group's own element in its radio-group kind — name, description, required, invalid, disabled and orientation, once (FGR-25): the host has no role, and nothing is tied across a shadow root. The field group gives disabled to each radio button by the tanrei-field-connect event it already answers — a slotted radio button's event passes the field group on its composed path before it reaches the host — and writes the announcement of RGP-8. The host takes disabled from outside by itself — from a field group around it (FGR-15) and from <fieldset disabled> — and gives the field group inside the three together; the inner field group's own question to a group around it is stopped at the host, so that an outer field group counts one member, the radio group (a frame's question, by contrast, leaves). What the host keeps is what a frame cannot do. A form-associated custom element (static formAssociated = true): name is a web attribute, not a spec prop; the value attribute is the first value and what a reset returns to, the value property is the live one; it submits value, restores it on navigation (formStateRestoreCallback), follows <fieldset disabled> and reports valueMissing while required and value is empty, anchored on the radio button that is the tab stop, with the browser's own message for an unanswered set of radio buttons, in the browser's language — a required native radio button is asked once — and, where a browser gives none, errorText, else the required text, else label. A radio button finds its group with closest('tanrei-radio-group') when it connects; the group's radio buttons are those among its descendants whose nearest radio group it is — direct children or deeper, inside the product's own elements — found again when its child list changes (a MutationObserver: slotchange does not fire for a change below a slotted element); a radio button inside a shadow root of the product's own is found by neither side and groups by its name there. The group sets each option's checked, and its radio buttons take their tab stop, aria-posinset and aria-setsize from it again whenever its options change (RGP-11); it handles the arrow keys on the host. A radio button's own disabled attribute is never written, so an enabled group cannot enable what the product disabled. A radio button inside a group fires no input and no change at all — stopped at the group they would still pass a capture listener on an ancestor — it tells its group, which sets value and fires input and one change, a bubbling CustomEvent whose detail.value is the value; a listener set on a radio button inside a group hears nothing. A press on the frame's texts selects nothing and moves no focus into the group (RGP-6); the browser's own answer to a press on plain text — focus leaves what had it — is not prevented, because preventing it would prevent selecting the text. The host is not focusable and does not delegate focus, because its controls are light-DOM children; focus() moves to the radio button that is the stop. Radio buttons with one name and no tanrei-radio-group still form a group by themselves (RAD-25); inside a <tanrei-field-group kind="radio-group"> they are this component one level down. Attributes are kebab-case: label-hidden, supporting-text, error-text, required-mark, required-text, optional-text. The host is display: block. Without script the element is not defined and draws nothing of its own, as every Tanrei element with a shadow root; a page that must work without script writes a native fieldset of native radio buttons of one name, which submits the same entry. Words: the element has no words of its own; required-text and optional-text left empty read the product's words through the field group. Its field group gives each radio button the error condition, and the radio button draws its error form (RGP-8, FGR-8, RAD-26). readOnly is the readonly attribute: each radio button it holds is read-only while it is (their own readonly unchanged), the arrow keys move focus without selecting, and aria-readonly is written on the field group's radiogroup element, beside the other states it carries. TanreiRadioGroup("Delivery", selection: $delivery, supportingText:, errorText:, error:, required:, optional:, orientation:) { TanreiRadioButton("Standard", value: Delivery.standard) … }, generic over a Hashable value: selection is a Binding<V?>, nil for none, and writing it is the change event. This is the container the radio button already uses to form a group on this platform (its name), which so far drew nothing; it gains the frame, which is the platform's field group itself in its radio-group kind (FGR-25), and the radio buttons inside read the selection from it. labelHidden follows .labelsHidden() and disabled follows .disabled(_:). The options are laid out by the field group's Layout (a column, or a row that wraps). iOS has no radio group trait: the container is .accessibilityElement(children: .contain) with the label and the description as its label and hint, and required and invalid are added to the hint in words. No forms: RGP-9 does not apply. The product's words are read from the \.tanreiWords environment value. Not built on this platform yet: in error every option is drawn in its error form (RGP-8, RAD-26). TanreiRadioGroup(label = "Delivery", value = delivery, onValueChange = { delivery = it }, supportingText = …, isError = …, required = …, optional = …, orientation = …) { TanreiRadioButton(value = "standard", label = "Standard") … }: controlled, as Compose is — the group calls onValueChange and the caller moves value. The content runs in a TanreiRadioGroupScope whose TanreiRadioButton(value, label) takes selected and its click from the group; the group is built around TanreiFieldGroup in its radio-group kind (FGR-25), which applies Modifier.selectableGroup() itself, so the product does not, and has the name, the description and the error in its semantics. enabled is the inverse flag. A Column or a FlowRow. The bare TanreiRadioButton(selected, onClick) inside a selectableGroup() stays for options that are not laid out by a group (RAD-25). No forms: RGP-9 does not apply. The product's words are read from LocalTanreiWords. Not built on this platform yet: in error every option is drawn in its error form (RGP-8, RAD-26). TanreiRadioGroup<T>(groupValue:, onChanged:, labelText: 'Delivery', helperText: …, errorText: …, isError: …, required: …, optional: …, orientation: …, children: [TanreiRadioButton<T>(value:, label:), …]). This is the TanreiRadioGroup<T> the radio button already stands in on this platform, which so far drew nothing and took a semanticLabel; it gains the frame, which is the platform's field group itself in its radio-group kind (FGR-25), and labelText with labelHidden replaces semanticLabel. groupValue is nullable for none; enabled is the inverse flag. A Column or a Wrap; the radio-group semantics, the arrow keys and the single tab stop stay where they are. FormField integration is the product's: RGP-9 does not apply. The product's words are read from the TanreiWords inherited widget, which the app fills from its own localizations. Not built on this platform yet: in error every option is drawn in its error form (RGP-8, RAD-26). <RadioGroup label="Delivery" value={delivery} onValueChange={setDelivery} supportingText="…"><RadioButton value="standard" label="Standard" />…</RadioGroup>: controlled. This is the RadioGroup parent the radio button already uses on this platform, which so far drew nothing; it gains the frame, which is the platform's field group itself in its radio-group kind (FGR-25), and takes selected and onSelect off its children: an option inside reads both from the group's context. accessibilityRole="radiogroup" with accessibilityLabel and accessibilityHint; required and invalid are said in the hint, because React Native has neither state for a group. A column, or a row with flexWrap. Follows the app's direction where an ancestor's is not known. No forms: RGP-9 does not apply. The product's words are read from the TanreiWordsProvider context. Not built on this platform yet: in error every option is drawn in its error form (RGP-8, RAD-26).

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-field-group-error-text-gap sys.space.space50 4px
--tanrei-comp-field-group-field-min-width — 144px
--tanrei-comp-field-group-gap-inline sys.space.space200 16px
--tanrei-comp-field-group-gap-controls sys.space.space100 8px
--tanrei-comp-field-group-gap-fields sys.space.space300 24px
--tanrei-comp-field-group-label-gap sys.space.space100 8px
--tanrei-comp-field-group-requirement-asterisk-gap sys.space.space25 2px
--tanrei-comp-field-group-requirement-gap sys.space.space100 8px
--tanrei-comp-field-group-requirement-mark sys.behavior.requirement-mark 0
--tanrei-comp-field-group-standard-disabled-error-text-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-error-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-group-standard-error-text-color sys.color.error #cd262f
--tanrei-comp-field-group-standard-error-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-group-standard-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-group-standard-error-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-group-standard-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-group-standard-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-group-standard-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-group-supporting-text-gap sys.space.space50 4px
--tanrei-comp-field-group-label-typescale sys.typescale.label 500 14px/1.4286
--tanrei-comp-field-group-supporting-text-typescale sys.typescale.label-small 500 12px/1.3333

测试样例

作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。

生成自 specs/radio-group.spec.json, tokens/comp/field-group.tokens.json, design/tokens/

单选组没有自己的令牌。上表列出的是字段组的令牌,单选组的框架就是用它们绘制的:重新设定字段组样式的设计系统,也就同时重新设定了每一个单选组的样式。每个单选按钮则用单选按钮自己的令牌绘制。

各个成员提供什么

绘制的内容 属于谁
标签、必填和选填标记、描述、错误文本、各行之间以及单选按钮之间的距离 字段组,来自 comp.field-group.*
每个单选按钮:圆环、填充、标签、状态层、焦点环、禁用时的样子 单选按钮,来自 comp.radio-button.*
其余的一切 没有:单选组什么都不绘制

单独调整某一个组

设计系统为框架设定一次样式,每一个字段组、单选组和复选框组都随之生效。要单独调整某一个实例,就在这个实例上设置:在 Web 上,设置在单选组上的字段组令牌会传到它里面的框架。

<tanrei-radio-group label="配送方式"
  style="--tanrei-comp-field-group-gap-controls: 12px">…</tanrei-radio-group>

Web 上绘制方式的不同之处

  • 这个元素的 shadow root 里有一个包着插槽的 <tanrei-field-group kind="radio-group">。单选按钮仍然是你自己的子元素。导入这个元素,两个成员也随之完成定义。
  • 这个元素是与表单关联的元素。它接受 name 和 form,并以 name 为名提交 value——选中的单选按钮被你单独禁用时也一样。它的单选按钮无论带什么 name,都不提交任何内容。它重置为 value 属性(attribute)的值,跟随被禁用的 <fieldset>;设置了 required 而 value 为空时,它用浏览器对没有回答的一组单选按钮自己的说法报告缺少值。
  • change 是 CustomEvent,它的 detail.value 是新的值;在它之前会触发 input。组内单选按钮上的监听器两者都收不到。
  • 部件:field-group 是框架,它的部件在这里的名称与下一层相同:label、requirement-label、supporting-text、error-text。你自己的单选按钮像在任何地方一样设定样式,用 tanrei-radio-button::part(…)。
  • 这个元素自己不能获得焦点。focus() 会把焦点交给作为停靠点的那个单选按钮,页面自己的 <label for> 也会把焦点移到那里。
  • 这个元素没有角色:单选组是它里面的字段组。用 label 为它命名——标签绘制在别处时再加上 label-hidden——绝不要用 aria-label,它在这里不为任何东西命名。
  • 属性(attribute)用 kebab-case 书写:label-hidden、supporting-text、error-text、required-mark、required-text、optional-text。
  • 在元素完成定义之前,以及没有脚本时,它自己什么都不绘制:单选按钮显示为普通内容。必须在没有脚本时也能工作的页面,请写一个原生的 <fieldset>,里面放同一个 name 的原生单选按钮,它提交的是同一个条目。

指南

何时用单选组,何时用别的

用户要做的事 使用
在表单或设置里,从少量选项中给出一个值,而这些选项都值得同时看到——最多五个左右 单选组。
改变下方显示的内容:哪个视图、哪个示例、哪个时间段 标签页。
从很多选项中给出一个值,或者空间不够 下拉选择框。
把多个值作为一个答案给出 复选框组。
手写同一组,以单选组没有的方式来排布它 kind 为 radio-group 的字段组,里面是同一个 name 的单选按钮(下一层)。
打开或关闭一样东西,并立即生效 开关。
按下并排放着、各自独立切换的按钮 按钮组。

区分单选组和标签页,看的是这次选择做了什么,而不是它的外观:被保存或被提交的值是单选组;选择要看什么,是标签页。

值

  • value 是选中的单选按钮的值;没有选中任何一个时,是空字符串。设置它就是选中;监听 change 可以得知用户的选择。
  • 每选择一次,组触发一次 change,带有组此时已经保存的值。你设置 value 时不触发,再次激活已选中的选项时也不触发。
  • 用户无法让单选组回到“什么都没选”。如果不作答也是一个真正的答案,就加一个这样说的选项(“无”“无偏好”),或者一开始就选中一个选项。
  • 选择跟随焦点:方向键一边移动一边选中。如果一次选择会立即产生代价高昂或看得见的效果,就从一个用来确认的按钮那里取得答案,而不是从 change。
  • 没有任何单选按钮与之对应的值,会显示为什么都没选,并且保留下来:对应的选项可能稍后才出现。

布局

  • orientation="vertical"(默认)把选项叠成一列。优先使用它。
  • orientation="horizontal" 把选项排成一行,放不下时换行。只把它用于两三个简短的选项:一行一旦换了行,就不容易看出哪个标签属于哪个圆环。

两种布局下方向键的行为相同,都按给定的顺序移动,而不是按换行后选项落在哪里。

错误

error 和 errorText 由你来设置:组绝不会让自己进入错误状态,即使它必填却没有回答。处于错误状态的组在选项下方绘制错误文本,暴露为无效,并让每个单选按钮都以错误形态绘制:圆环和圆点采用错误颜色,与复选框组里的方框一样。禁用的选项不显示错误颜色,因为它不可能是纠正这个错误的答案。单选按钮自己不会暴露为无效;这一点由组说一次。

值的变化不会清除错误。答案可以接受时,由你清除它。

只读

只读的组就地显示它的答案,供人阅读而不能更改:例如确认步骤,或只有部分人可以更改的选择。Tab 仍然从选中的单选按钮进入它,方向键在选项之间移动但不选中任何一项,值随表单提交。必填但未作答的只读组不会被表单拒绝,因为这个人无法作答。它绘制为启用的组,只是没有悬停和按下的反馈。

推荐

  • 用这些选项所回答的问题来为这一组命名:“配送方式”。
  • 给每个单选按钮一个在组内唯一的 value,以及它自己的标签。
  • 选项要少。只有一个选项时该用复选框;选项很多时该用下拉选择框。

避免

  • 不要在单选组里放其他任何可以操作的东西:不放复选框,不放按钮,不放字段。
  • 不要给单选组里的单选按钮设置 name。设置了也会被忽略:这个组就是它们的组。
  • 不要为了给单选组加标签而把它包在字段或字段组里。它有自己的标签。单选组可以是字段组的一个成员,由那个字段组为更大的一组命名(“配送信息”)。
  • 不要用禁用整个组来充当只读:禁用的组什么都不提交,也无法到达。请使用 readOnly,它让这组选项留在焦点顺序中并提交它的值。

无障碍

角色
radiogroup
标注
label names the radio group, drawn or not (labelHidden). Each option keeps its own name, its own label beside it, and says its position in the set. The group's description is supportingText, then errorText while error is true, then optionalText while it is optional; a required group carries the required state, and a group in error the invalid state, so neither is said in words a second time. The requirement tag and the asterisk are hidden.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab / Shift+Tab Moves focus into the group — to the selected option, or to the first enabled one while none is selected — and out of it. The group has one stop and is not a stop itself (RGP-6).
Down Arrow / Right Arrow Moves to the next enabled option and selects it, wrapping from the last to the first; in a right-to-left layout Left Arrow does what Right Arrow does here (RGP-5). While the group is read-only it moves focus and selects nothing (RGP-20).
Up Arrow / Left Arrow Moves to the previous enabled option and selects it, wrapping from the first to the last; in a right-to-left layout Right Arrow does what Left Arrow does here. While the group is read-only it moves focus and selects nothing.
Space Selects the focused option if it is not selected (after Tab into a group with nothing selected); does nothing on the selected one. Nothing while the group is read-only.
Enter Nothing on the web, where it submits the form; selects where the platform activates any focused control with Enter (Android, tvOS), as RAD-1 says.

备注

  • Not a target itself: the group takes no press and is not a focus stop (RGP-6), so minimumTarget holds through its radio buttons, each of which keeps a whole touch target of its own at the distance the group puts between them (RAD-9, RAD-21, RGP-2).
  • The radio group is a field group that owns a value: its frame is the field group itself, in its radio-group kind (RGP-1, FGR-25), and it adds what a frame cannot have — one value, one change, the arrow keys and the one tab stop kept for the set, and a place in a form as one control. It has no tokens of its own. One level down is the same field group written by hand around radio buttons of one name: the same picture and the same radio group, without the value. A set of checkboxes that is one answer is a checkbox group, which holds a list of values where this holds one; a set of switches, and checkboxes that each have a name of their own, need none of that and stay a field group.
  • Selection follows focus: an arrow key selects as it moves, as every platform's own radio buttons do, so the arrow keys never leave focus on an option that is not the answer. A product in which a selection has an expensive or a visible effect at once (it saves, it loads) should take the answer from a button that confirms it, not from change.
  • A person cannot return a radio group to "nothing selected". Where no answer is a real answer, add an option that says so ("None", "No preference"), or let the form start with one option selected. The product can still set value to the empty string.
  • A group in error is told by its error text, which is words (WCAG 3.3.1), and by the invalid state; the error color its radio buttons take (RAD-26) is one more sign and never the only one (WCAG 1.4.1), and no radio button is exposed as invalid by itself. The error text is announced when it appears under the person's focus and is otherwise read with the group's description (RGP-8, RGP-10); it never takes focus away.
  • When a radio group, and when something else. One value from a few options that are all worth seeing at once, in a form or a setting: a radio group, of up to about five options. A choice that changes the content shown below it — which view, which sample, which period: tabs. One value from many options, or where there is little room: a select. Several of several: a checkbox group. On or off, taking effect at once: a switch. Buttons side by side that each toggle on their own: a button group. A segmented button exists only in a design system that ships one.
  • Prefer a column. A row is for two or three short options; once it wraps, which label belongs to which ring is harder to see.
  • On the web the radio group that assistive technology is told of is the field group's own element inside the component's shadow root, which carries the radiogroup role in its radio-group kind, around the slot that holds the radio buttons; the role's required and invalid states are why a set of radio buttons is not a plain group. iOS has no radio group trait: the container is an accessibility container whose label is the group's, and each option says its position through its value, as the radio button's spec notes.
  • Heights are the texts' and the options': at 200% text size every text wraps, a row becomes a column, and nothing is cut (WCAG 1.4.4, 1.4.10).
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
  • A radio button the product disabled by itself keeps its place in the count, is passed over by the arrow keys and Tab, and stays selected while value is its value; its value is still submitted, because the value is the group's (RGP-7, RGP-9).
  • Read-only (RGP-20) is a set that shows its answer in place and keeps it in the focus order, so that it is read with its form — on a review step, or where only some people may change it: the group says read-only (aria-readonly on the element with role radiogroup on the web, which WAI-ARIA allows on radiogroup and not on radio), each option says whether it is the chosen one, and the arrow keys move among them without choosing, as React Aria's isReadOnly radio group does. Where the answer needs no control at all, write it as text; where no one may reach the set, it is disabled.

生成自 specs/radio-group.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 一个包含其单选按钮的单选组,由 label 命名,无论它是画出来的,还是用 labelHidden 隐藏的。
  • 设置了 required 时的必填状态、error 为 true 时的无效状态、disabled 为 true 时的禁用状态,以及它的方向。
  • readOnly 为 true 时的只读状态。此时每个单选按钮都是只读的:Tab 和方向键仍然移动焦点,但任何操作都不会改变 value。
  • 组的描述是 supportingText;error 为 true 时,后面接 errorText;组为选填时,再接 optionalText。requiredText 不在其中:必填状态已经说明了这一点。
  • 每个单选按钮都说明它在这一组中的位置和这一组的大小,禁用的选项也计算在内。
  • 焦点顺序中只有一个停靠点:选中的单选按钮,或者第一个可用的单选按钮。Tab 从这里进入组,也从这里离开;方向键在组内移动,跳过禁用的选项,到两端时循环。在从右到左的布局中,左方向键和右方向键互换。
  • disabled 会禁用每一个单选按钮。启用的组绝不会启用你自己禁用的单选按钮:方向键和 Tab 会跳过它,它在计数中保留自己的位置,并且只要 value 是它的值,它就保持选中。
  • 焦点位于某个单选按钮上时出现或改了措辞的错误,会以礼貌(polite)的方式朗读一次。焦点绝不会因为错误而移动。
  • 必填和选填小标签以及星号对辅助技术隐藏。
  • 文本放大到 200% 时,所有文本都会换行,排成一行的选项变成一列,不会有任何内容被截断。
  • 在强制颜色模式下,所有文本都采用系统的文本颜色,必填和选填小标签会多出一道边线。
  • 在从右到左的布局中,各行从右侧开始,排成一行的选项从右侧排起,每段文本保持自己的方向。
  • 在 Web 上,它是所在表单的一个控件:以 name 为名提交 value,重置时回到最初的值,跟随被禁用的 fieldset,并在必填却没有回答时向表单自己的校验报告缺少值。
  • 组里没有单选按钮、组里有其他控件、组没有标签、两个选项的值相同,或者值没有对应的选项,都会在调试构建中报告。

Web 上的已知差距:

  • 在元素定义之前,单选按钮出现在页面上时没有组的标签、描述、布局和选择。

需要你做的事

  • 为组提供 label。
  • 自己校验回答,设置 error,并编写说明如何改正的 errorText。
  • 必填却没有回答的组,会像选择框一样被表单自己的校验用浏览器的话拒绝。若要用产品自己的话来说,就像 GOV.UK 那样:在表单上加 novalidate,并在提交时为组设置 error 和 errorText。
  • 给出必填和选填的文字:把 required-text 和 optional-text 写在组上,或者用 setWords({ requiredText, optionalText }) 为整个产品只给一次。Tanrei 不自带任何文字:没有 requiredText 时,小标签的位置上是一个星号;没有 optionalText 时,不绘制小标签,也不朗读任何内容。参见产品的文字。
  • 提交失败之后,还要在表单上方说明哪些内容被拒绝,并链接到这个组:使用一个带 live、处于错误语义状态的提示块。