复选框组
提出一个可以有多个答案的问题,选项不多,同时全部显示出来——同一个标签之下的一组复选框,带有一段描述和一条错误;它在一个名称之下保存选中值的列表,还可以带上一个把它们全部选中的复选框。
- 类别
- 选择
- 令牌
comp.field-group- 层级
- 组合
- 构成自
- Field group, Checkbox
- Web
tanrei-checkbox-groupschedule - SwiftUI
TanreiCheckboxGroupradio_button_unchecked - Jetpack Compose
TanreiCheckboxGroupradio_button_unchecked - Flutter
TanreiCheckboxGroupradio_button_unchecked - React Native
CheckboxGroupradio_button_unchecked
生成自 docs/status/status.json, specs/checkbox-group.spec.json
一个复选框组,以及一个带有全选复选框的复选框组
一个问题的答案是一个列表,而选项又少到可以同时全部显示出来(最多七个左右)时,就使用复选框组:通知的方式、发送报告的日子、筛选列表所用的种类。答案是一个值——选中值的列表——表单在一个名称之下提交它。
组成部分是一个字段组和若干复选框。字段组是框架:标签、必填或选填标记、描述、错误,以及排成一列或一行的布局。复选框保存值:每一个都是带有自己标签的一个选项,其中哪些被选中,就构成了这个列表。复选框组自己什么都不绘制,也没有令牌。它在这两者之上添加的,是每个产品本来都要自己写的状态:列表和针对它的一个 change;表单里的一个名称,连同重置,以及表单自己对“必填的一组不能为空”的校验;还有全选复选框的三种状态。
它的宽度与所在的位置相同。
在 Web 上它是 <tanrei-checkbox-group>。其他平台尚未实现。
每个复选框就是由它的 value 命名的那个选项。组上的 value 是选中值的列表,按复选框的顺序排列。列表里有、却没有任何复选框拥有的值会被保留——被搜索筛到看不见的选项,仍然是选中的。
<form>
<tanrei-checkbox-group name="notify" label="通知方式"
supporting-text="可多选。"
required required-text="必填"
error-text="请至少选择一种通知方式。">
<tanrei-checkbox value="email" label="邮件" checked></tanrei-checkbox>
<tanrei-checkbox value="text" label="短信"></tanrei-checkbox>
<tanrei-checkbox value="push" label="推送通知"></tanrei-checkbox>
</tanrei-checkbox-group>
</form>
<script type="module">
import '@tanrei/web/components/checkbox-group';
const group = document.querySelector('tanrei-checkbox-group');
group.addEventListener('change', (event) => {
// event.detail.value 是新的列表;group.value 已经与它相同。
group.error = event.detail.value.length === 0;
});
// 产品以列表的形式设置答案。它是属性(property):没有 value 属性(attribute)。
group.value = ['email', 'push'];
</script>import { Checkbox, CheckboxGroup } from '@tanrei/react';
const [channels, setChannels] = useState(['email']);
<CheckboxGroup name="notify" label="通知方式" supportingText="可多选。"
value={channels} onChange={(event) => setChannels(event.detail.value)}
error={channels.length === 0} errorText="请至少选择一种通知方式。">
<Checkbox value="email" label="邮件" />
<Checkbox value="text" label="短信" />
<Checkbox value="push" label="推送通知" />
</CheckboxGroup>在标记里,最初的值写在 HTML 本来写它的地方:各个复选框上的 checked。一旦产品设置了 value,哪些复选框被选中就由 value 决定。
给出 selectAllLabel,组就会在第一个复选框之前多带一个复选框,以这段文本命名。用户可以切换的复选框全部选中时,它是选中的;只有一部分选中时,它是半选的;一个都没有选中时,它是未选中的。在未选中或半选时按下它,会把它们全部选中;在选中时按下它,会把它们全部取消选中。它不是列表里的一项,也不提交任何东西。
<tanrei-checkbox-group name="days" label="报告发送日" select-all-label="每个工作日">
<tanrei-checkbox value="mon" label="星期一"></tanrei-checkbox>
<tanrei-checkbox value="tue" label="星期二"></tanrei-checkbox>
…
</tanrei-checkbox-group>你自己禁用的复选框保持原样,无论选中与否,也不计算在内:全选复选框只计算用户可以切换的那些选项。这一组的标签——“以上全部”“每个工作日”——要用问题本身的措辞来写;它绝不会从产品的文字里读取。
用它的两个成员手写出来的同一组,画面是一样的,对辅助技术来说也是同一个组:
<tanrei-field-group label="通知方式" supporting-text="可多选。"
required required-text="必填">
<tanrei-checkbox name="notify" value="email" label="邮件" checked></tanrei-checkbox>
<tanrei-checkbox name="notify" value="text" label="短信"></tanrei-checkbox>
<tanrei-checkbox name="notify" value="push" label="推送通知"></tanrei-checkbox>
</tanrei-field-group>手写出来的同一组:一个字段组,里面有若干复选框
手写的字段组和复选框没有做的事,是保存答案:
| 方面 | 手写 | 复选框组 |
|---|---|---|
| 列表 | 由产品从每个复选框上读取 | value,以及一个 change |
| 设置答案 | 每个复选框上的 checked |
value = […] |
| 重置和恢复 | 每个复选框各管各的 | 作为一个控件 |
| 必填的一组留空 | 由产品检查 | 表单自己的校验会拒绝 |
| 复选框没有显示出来的值 | 丢失 | 保留 |
| 全选复选框 | 由产品编写,用 indeterminate |
selectAllLabel |
| 表单提交的内容 | 同样的条目 | 同样的条目 |
当这一组不是一个答案时,就往下走一层:
| 你需要 | 使用 |
|---|---|
| 把少量选项中的几个作为一个名称之下的一个答案 | 复选框组 |
| 在表单里各有自己名称的复选框,或者里面有不是复选框的东西的一组 | 一个字段组,里面放复选框 |
| “全部”的含义按你自己的规则来定——某一页上的每一项、每个匹配的项 | 一个字段组,里面放一个由你自己设置 indeterminate 的复选框 |
| 少量选项中的一个 | 单选组 |
| 一样东西的开或关 | 复选框;立即生效的地方用开关 |
| 许多选项中的几个,或者空间很小 | 下拉选择框,或者可以多选的列表 |
排成一行、被拒绝的回答、始终打开的选项,以及禁用的组
| 方面 | 来自字段组 | 来自复选框 | 组自己的 |
|---|---|---|---|
| 属性 | label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText, orientation |
每个复选框的 value、label 和 disabled,写在复选框上 |
value, selectAllLabel |
| 事件 | — | 没有:组里的复选框自己不触发任何事件 | change |
| 令牌 | comp.field-group.* |
comp.checkbox.* |
没有 |
- 必填表示至少选一个。最少或最多选几个(“最多选三个”)是你自己的规则:在
change里读取列表的长度,设置error,并用你自己的话把这个数目写进errorText。 - 错误是整组的。
error把errorText画在最后一个复选框下方,并让每个复选框进入它自己的错误状态。组绝不会自己设置它,值的变化也不会清除它。组没有出错、而其中一个复选框出错时,用的是那个复选框自己的error。 - 禁用。禁用的组保留它的值,什么都不提交。在启用的组里单独禁用的复选框保留它的条目,并随组一起提交:始终打开的选项,就是一个禁用且选中的复选框,旁边不需要隐藏字段。
- 它没有只读状态,因为复选框没有。
框架做什么,也就是必填标记、描述、错误以及错误怎样朗读、一列和会换行的一行,写在字段组的页面上。复选框做什么,写在复选框的页面上。
复选框组没有自己的令牌。它的框架读取字段组的令牌,它的复选框读取复选框的令牌,无论这些令牌设置在哪里。要改动某一个实例,就在它上面设置成员的令牌:
<tanrei-checkbox-group label="通知方式"
style="--tanrei-comp-field-group-gap-controls: 12px">…</tanrei-checkbox-group>构成
| 部件 | 说明 |
|---|---|
1 label | The field group's label: names the set — the question — above the checkboxes. The requirement mark follows it. |
2 requirement-label · 可选 | The field group's requirement mark: a small tag that is not a control, saying an answer is required or optional; or, for required, an asterisk after the label. |
3 supporting-text · 可选 | The field group's description: a line under the label, before the checkboxes, that says how to answer. It stays while the group is in error. |
4 select-all · 可选 | One more checkbox before the first, brought by the group where selectAllLabel is given: the Checkbox component, checked, unchecked or indeterminate from the checkboxes of the set, and checking or unchecking them all. It is no entry of the value. |
5 checkboxes | The checkboxes of the set, in the order given, in a column or in a row that wraps. Each is the Checkbox component with its own label, drawn from its own tokens; which of them are checked is the group's value. |
6 error-text · 可选 | The field group's error text: the reason the answer was refused or is missing, below the last checkbox. |
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | strings | | The values of the checked checkboxes: a list of strings, empty while none is checked (the platforms whose idiom it is take a set). The checkbox group sets it when a person switches a checkbox, and emits change; the product may also set it. It is the set's value: what a form submits under the group's one name. |
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 empty (CKG-8). |
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 Every checkbox of the set is drawn in its own error condition and exposed as invalid. |
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 (CKG-6, CKG-8). |
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 means at least one checkbox checked: on the web an empty list is reported to the form's own validation (CKG-8). |
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 |
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 |
selectAllLabel | string | "" | Not empty: the group brings one more checkbox before the first, labelled by this text ("All of these", "Every weekday"), that checks or unchecks every enabled checkbox of the set at once and shows checked, unchecked or indeterminate from them (CKG-12, CKG-13). Empty, the default: there is none. The instance's own words: it names this set, so it is not read from the product's words. |
事件
| 事件 | 说明 |
|---|---|
change | The person switched a checkbox of the set, or activated the select-all checkbox; carries the new list, which the group already holds. Emitted once per action, by the group and not by its checkboxes. Not emitted when the product sets value. |
条目
该组件重复排列的条目种类。测试样例按顺序列出条目;各平台以各自的惯用方式为每个种类命名。
checkbox
A choice of the set: the Checkbox component with its own label, checked by the group's value, placed, spaced, disabled and put in its error condition by the group's frame, otherwise drawn and pressed as it is anywhere.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | "" | What the choice stands for: an entry of the group's value while this checkbox is checked. Unique in the group and not empty. The group reads it from the item; it is not a prop of the Checkbox (on the web, the checkbox's own value attribute). |
label | string | "" | Its own label, as the Checkbox's label. |
disabled | boolean | false | As the Checkbox's disabled: one checkbox of an enabled group may be disabled by itself. The person cannot switch it, the select-all checkbox leaves it alone, and its entry stays in the value while the value holds it. |
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 条目 | tanrei-checkbox | TanreiCheckbox | TanreiCheckbox | TanreiCheckbox | Checkbox |
value · 属性 | value | value | value | entry | entry |
label · 属性 | label | title | label | label | label |
disabled · 属性 | disabled | disabled | enabled | enabled | disabled |
| 备注 | A tanrei-checkbox child: inside a tanrei-checkbox-group it takes its checked from the group's value and its own value attribute, submits nothing itself and fires no input or change of its own (CHK-23). A child with no value attribute has no value: the on a bare checkbox submits by default is not read inside a group. | TanreiCheckbox.value is the checked state on this platform, so the entry a checkbox stands for has a name of its own: TanreiCheckbox<T>.inGroup(entry:, label:, enabled:) is the Checkbox itself, checked from the group and without value and onChanged. | Checkbox's value is the checked state on this platform, so the entry a checkbox stands for has a name of its own: <Checkbox entry="email" label="Email" /> inside a CheckboxGroup is the Checkbox itself, checked from the group's context and without value and onValueChange. |
状态
行为
以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。
| ID | 需求 |
|---|---|
CKG-1 | THE checkbox group SHALL be a field group whose spacing is controls, around its checkboxes: 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 (FGR-2), the requirement, its mark and its words (FGR-4, FGR-5, FGR-6, FGR-24), the description, which stays while the group is in error (FGR-7), the error text (FGR-8), the column and the row that wraps (FGR-3, FGR-13), the disabled frame (FGR-14) and what an unknown value of required, optional, requiredMark or orientation does (FGR-22), 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 checkboxes but the field group's, and SHALL leave each checkbox drawn from the checkbox's own tokens at the checkbox's own size (CHK-4, CHK-12, CHK-15). |
CKG-2 | THE checkbox group SHALL hold one value, a list of strings, and show as checked exactly those of its checkboxes whose value is in the list; WHILE the list is empty none SHALL be checked. WHEN the product sets value THE checkbox group SHALL check and uncheck its checkboxes to match, each over the checkbox's own selection motion (CHK-13), without emitting change and without moving focus. THE checkbox group SHALL keep in value an entry that is the value of none of its checkboxes, because the checkbox it belongs to may arrive later or be out of the set for now (a list of choices narrowed by a search), and SHALL give it back with the rest in change (CKG-4) and to a form (CKG-8). IF value holds one entry twice THEN THE checkbox group SHALL treat it as held once; IF two checkboxes carry one value THEN both SHALL be shown checked or unchecked together, and switching either SHALL switch the entry; IF a checkbox carries no value THEN it SHALL be drawn unchecked and a press on it SHALL change nothing and emit nothing, because the list has no entry to stand for it; and in debug builds THE checkbox group SHALL report each of the three. |
CKG-3 | WHERE the platform lets the product mark one checkbox as checked by itself (on the web, checked on a child element) and the product has not set value, THE checkbox group SHALL take as its value the values of the checkboxes so marked, in the order given, so that markup that says checked on some of its checkboxes is a group with that value, and a checkbox that arrives so marked SHALL add its value to the list. WHEN the product has set value, value SHALL decide: a checkbox that is first drawn or arrives marked against it SHALL be shown as value says. WHEN the product sets or clears checked on a checkbox the checkbox group already holds THE checkbox group SHALL add that checkbox's value to the list or take it out, without emitting change, so that the two never disagree. |
CKG-4 | WHEN the person switches one of the group's checkboxes — by pointer, touch, Space, a click on its label or assistive technology, as CHK-1 says of the checkbox — THE checkbox group SHALL add that checkbox's value to the list where the checkbox became checked and take it out where it became unchecked, and SHALL emit change exactly once, carrying the new list, with value already set when the event is received. The list it then holds SHALL be the values of its checked checkboxes in the order the checkboxes are given, not the order in which they were checked, followed by the entries that belong to no checkbox, in the order they had. No other checkbox SHALL change, and nothing the checkbox emits SHALL reach a listener on the group as a second change (CHK-23). A person can empty the list: unchecking the last checked checkbox emits change with an empty list. |
CKG-5 | THE checkbox group SHALL leave every enabled checkbox its own place in the focus order, in the order given, as the platform has it for checkboxes: Tab and Shift+Tab SHALL move from one checkbox to the next and out of the group at either end, and THE checkbox group SHALL handle no key — no arrow key moves focus or checks, and there is no single stop and no roving focus — because each checkbox is a choice of its own that a person may stop at and switch; Space is the checkbox's (CHK-1), and Enter is left to the form on the web. THE checkbox group SHALL NOT be a stop itself, and a press on its label, its requirement mark, its description or its error text SHALL check nothing and move no focus into the group (FGR-12). WHEN the product asks the group itself for focus, where the platform has such a call, THE checkbox group SHALL give focus to the first enabled checkbox it shows, the select-all checkbox where one is drawn. |
CKG-6 | WHILE disabled is true THE checkbox group SHALL be a disabled field group (FGR-14): its frame drawn from the field group's disabled tokens and every checkbox it holds disabled as the checkbox's own disabled does (CHK-3, CHK-8), the checked ones still shown checked; it SHALL emit no change and SHALL keep value. WHILE disabled is false THE checkbox group SHALL NOT enable a checkbox the product disabled itself: that one keeps its entry in the list WHILE the list holds it, is shown checked or unchecked as value says, cannot be switched by the person, and is left as it is by the select-all checkbox (CKG-13). THE checkbox group SHALL have no read-only state, because a checkbox has none and a composed component adds no state its members have not. |
CKG-7 | THE checkbox group SHALL take from required and optional what the form asks of the set, as a field group does (FGR-4): required means that the list holds at least one entry. |
CKG-8 | WHERE the platform has forms that submit named values (the web) THE checkbox group SHALL take part in its form as one control: WHILE it is not disabled it SHALL submit one entry under its own name for each entry of value, in the list's order, and nothing WHILE the list is empty — what checkboxes of one name submit by themselves, so that whatever receives the form reads the same list from either; 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 the list is empty, in the product's words, because the platform has a message for one unchecked checkbox and none for a set; and its checkboxes, the select-all checkbox among them, SHALL submit nothing of their own, whatever name they carry (CHK-23), so that no entry is submitted twice. A checkbox the product disabled itself SHALL still have its entry submitted WHILE the list holds it, because the value is the group's and that checkbox's disabled says only that the person cannot switch it. WHERE the platform has no such forms value and change are the whole of it. |
CKG-9 | THE checkbox group SHALL be exposed to assistive technology as the field group it is, and SHALL add nothing to it: one group that contains its checkboxes, named by label whether or not the label is drawn (FGR-10); described by supportingText, errorText WHILE error is true, requiredText WHILE required is true and optionalText WHILE optional is true (FGR-11); and disabled WHILE disabled is true (FGR-14). A group has neither a required nor an invalid state, so the requirement is said in the product's words and each checkbox says invalid for itself. Each checkbox SHALL expose its own role, its name and its checked state (CHK-14), and the select-all checkbox its mixed state WHILE it is indeterminate. THE checkbox group SHALL NOT expose a second group around the first, SHALL give its checkboxes no position in a set, and SHALL NOT say how many are checked: a product that wants the count said writes it in supportingText. |
CKG-10 | WHEN a checkbox is added to the group, removed from it or moved, or its value or its disabled changes, while the group is shown THE checkbox group SHALL be laid out again as a field group is (FGR-23); SHALL check by value again (CKG-2), so that a checkbox that arrives with a value the list holds is shown checked, and SHALL keep the entry of a checked checkbox that leaves; SHALL set the select-all checkbox again (CKG-12); SHALL emit no change; and SHALL leave focus on the checkbox that has it. |
CKG-11 | THE checkbox group SHALL hold checkboxes and nothing else that can be operated. IF it holds no checkbox THEN THE checkbox group SHALL draw its rows without one and in debug builds report it (FGR-20); IF it holds another control, or a group of controls inside it, THEN in debug builds THE checkbox group SHALL report it and SHALL make no entry of its value from what that one holds. THE checkbox group SHALL NOT restyle a checkbox or change how it answers hover, focus or a press (CHK-5 to CHK-10), and SHALL NOT set indeterminate on a checkbox of the set: a checkbox of the set is in the list or it is not. |
CKG-12 | WHILE selectAllLabel is not empty THE checkbox group SHALL bring one more checkbox, the select-all checkbox: the Checkbox component itself, labelled by selectAllLabel, placed before the first checkbox of the set as one more control of the field group — the same distance from the next, in a column or in a row, the set not indented under it — and so disabled and put in its error condition with the others (CKG-6, CKG-7). It SHALL stand for the checkboxes of the set that the product did not disable one by one: checked WHILE each of them is checked, indeterminate WHILE some are and some are not, unchecked WHILE none is (CHK-4), and disabled WHILE there is no such checkbox. It SHALL have no value, SHALL be no entry of the list, and SHALL follow every change of value and of the set at once, whoever made the change. WHILE selectAllLabel is empty THE checkbox group SHALL bring none. |
CKG-13 | WHEN the person activates the select-all checkbox while it is unchecked or indeterminate THE checkbox group SHALL check every checkbox of the set that the product did not disable itself, as CHK-2 says of an indeterminate checkbox, and while it is checked SHALL uncheck every one of them; a checkbox the product disabled SHALL stay as it is, and an entry of value that belongs to no checkbox SHALL stay in the list. THE checkbox group SHALL emit change exactly once, carrying the list after the change (CKG-4), and nothing of the select-all checkbox's own SHALL reach a listener on the group. |
CKG-14 | THE checkbox group SHALL add nothing of its own to how its members answer the layout direction, enlarged text and forced colors. WHERE the layout direction is right-to-left it SHALL be mirrored as the field group is (FGR-18), SHALL take its direction from where it is used and follow a change of it at once (FGR-19), keeping its value and its focus, and SHALL lay out each of its texts by that text's own direction (FGR-21), each checkbox mirroring itself (CHK-15, CHK-20, CHK-21); it has no key to exchange (CKG-5). WHILE the text size is enlarged it SHALL grow as the field group does (FGR-16): every text wraps, a row becomes a column where one line no longer holds two checkboxes, and the boxes keep their size. WHERE the platform draws with a person's own forced colors it SHALL be drawn as the field group is there (FGR-17), the error carried by the words of errorText and by each checkbox's invalid state, never by a color of the group's. |
CKG-15 | 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 checkbox 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 group of the field group it is (CKG-9) — 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 checkbox group owns (aria-label, aria-labelledby, aria-disabled, aria-required, aria-invalid, aria-errormessage), which it SHALL move and not expose: its name is label and its states are its props (CKG-9). 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 checkbox group SHALL take it off that element. WHEN the product sets an aria- attribute the checkbox group owns THE checkbox group SHALL report it in debug builds. |
各平台名称对照
对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 组件 | tanrei-checkbox-group | TanreiCheckboxGroup | TanreiCheckboxGroup | TanreiCheckboxGroup | CheckboxGroup |
value · 属性 | value | selection | values | values | 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 |
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 |
selectAllLabel · 属性 | selectAllLabel | selectAllLabel | selectAllLabel | selectAllLabel | selectAllLabel |
change · 事件 | change | selection | onValuesChange | onChanged | onValueChange |
| 备注 | <tanrei-checkbox-group label="Notify me by" name="notify"><tanrei-checkbox value="email" label="Email" checked></tanrei-checkbox>…</tanrei-checkbox-group>; class TanreiCheckboxGroup; in @tanrei/react, CheckboxGroup with value and onChange. Composed as every composed element on the web is (a custom element whose shadow root holds its members' own elements); what differs here: the members that hold the value are the product's own children. The shadow root holds one <tanrei-field-group spacing="controls"> — the member's own element, not drawing code shared with it — around, in this order, the select-all <tanrei-checkbox part="select-all"> while select-all-label is given, and a <slot>; the product's checkboxes stay in its 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 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. Name, description and states are the field group's own fieldset and legend, once: the host has no role. The field group gives disabled, the error condition and the error text to each checkbox by the tanrei-field-connect event it already answers — a slotted checkbox's event passes the field group on its composed path before it reaches the host, so the group forwards nothing. value is a property only, an array that is copied in and out; there is no value attribute, because the first value is written where HTML writes it, as checked on the children (CKG-3), and that is also what a reset returns to where the property was never set. A checkbox finds its group with closest('tanrei-checkbox-group') when it connects; the group's checkboxes are those among its descendants whose nearest checkbox group it is, found again when its child list changes. A checkbox 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 a copy of the list; a listener set on a checkbox inside a group hears nothing. A form-associated custom element (static formAssociated = true): name is a web attribute, not a spec prop; it submits a FormData of one entry per value under name (ElementInternals.setFormValue), restores the list on navigation (formStateRestoreCallback), follows <fieldset disabled>, and reports valueMissing while required and the list is empty, anchored on its first enabled checkbox, with the message errorText, else the required text (the instance's or the product's), else label — never a browser's sentence for one unchecked box. The host is not focusable and does not delegate focus; focus() moves to the first enabled checkbox. Attributes are kebab-case: label-hidden, supporting-text, error-text, required-mark, required-text, optional-text, select-all-label. 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 checkboxes of one name, which submits the same entries. 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, and select-all-label is the instance's alone. | TanreiCheckboxGroup("Notify me by", selection: $channels, supportingText:, errorText:, error:, required:, optional:, orientation:, selectAllLabel:) { TanreiCheckbox("Email", value: Channel.email) … }, generic over a Hashable value: selection is a Binding<Set<V>>, and writing it is the change event; a set has no order to keep (CKG-4). The view is a TanreiFieldGroup around its content — the field group's own view, which reads comp.field-group from the environment, so one group is set apart with .tanreiTheme(_:) on it — and the checkboxes inside read and write the selection through the environment: TanreiCheckbox(_:value:) is the Checkbox with its isOn bound to membership. labelHidden follows .labelsHidden() and disabled follows .disabled(_:). The select-all checkbox is a TanreiCheckbox with isIndeterminate. No forms: CKG-8 does not apply. The product's words are read from the \.tanreiWords environment value. | TanreiCheckboxGroup(label = "Notify me by", values = channels, onValuesChange = { channels = it }, supportingText = …, isError = …, required = …, optional = …, orientation = …, selectAllLabel = …) { TanreiCheckbox(value = "email", label = "Email") … }: controlled, as Compose is — values is a Set<T>, the group calls onValuesChange and the caller moves it. The composable is a TanreiFieldGroup around its content, which runs in a TanreiCheckboxGroupScope whose TanreiCheckbox(value, label) is the Checkbox with checked and its click taken from the group; one group is set apart with a TanreiTheme around it. enabled is the inverse flag. The select-all checkbox is the TanreiCheckbox(state: ToggleableState, onClick) overload. No forms: CKG-8 does not apply. The product's words are read from LocalTanreiWords. | TanreiCheckboxGroup<T>(values:, onChanged:, labelText: 'Notify me by', helperText: …, errorText: …, isError: …, required: …, optional: …, orientation: …, selectAllLabel: …, children: [TanreiCheckbox<T>.inGroup(entry:, label:), …]): values is a Set<T>. The widget builds a TanreiFieldGroup around its children and an inherited scope that gives each checkbox its checked state and takes its change; one group is set apart with a TanreiTheme around it. enabled is the inverse flag. The select-all checkbox is a TanreiCheckbox with tristate. FormField integration is the product's: CKG-8 does not apply. The product's words are read from the TanreiWords inherited widget. | <CheckboxGroup label="Notify me by" value={channels} onValueChange={setChannels} supportingText="…" selectAllLabel="…"><Checkbox entry="email" label="Email" />…</CheckboxGroup>: controlled; value is an array of strings in the order of the checkboxes. The component renders a FieldGroup around its children and a context that gives each checkbox its checked state; one group is set apart with a provider around it. The select-all checkbox is a Checkbox with indeterminate. Follows the app's direction where an ancestor's is not known. No forms: CKG-8 does not apply. The product's words are read from the TanreiWordsProvider context. |
令牌
| 令牌 | 引用 | 解析值(浅色) |
|---|---|---|
--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 | — | |
--tanrei-comp-field-group-standard-disabled-error-label-color | — | |
--tanrei-comp-field-group-standard-disabled-error-required-asterisk-color | — | |
--tanrei-comp-field-group-standard-disabled-error-supporting-text-color | — | |
--tanrei-comp-field-group-standard-disabled-label-color | — | |
--tanrei-comp-field-group-standard-disabled-required-asterisk-color | — | |
--tanrei-comp-field-group-standard-disabled-supporting-text-color | — | |
--tanrei-comp-field-group-standard-error-text-color | sys.color.error | |
--tanrei-comp-field-group-standard-error-label-color | sys.color.on-surface | |
--tanrei-comp-field-group-standard-error-required-asterisk-color | sys.color.primary | |
--tanrei-comp-field-group-standard-error-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-field-group-standard-label-color | sys.color.on-surface | |
--tanrei-comp-field-group-standard-required-asterisk-color | sys.color.primary | |
--tanrei-comp-field-group-standard-supporting-text-color | sys.color.on-surface-variant | |
--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 为每个样例截取快照,以便相互比较。
default
value: ["email"] label: Notify me by items: 3 supporting
value: ["email"] label: Notify me by supportingText: Choose all that apply. items: 3 label-hidden
value: ["email"] label: Notify me by labelHidden: true supportingText: Choose all that apply. items: 3 required
value: ["email"] label: Notify me by supportingText: Choose all that apply. required: true requiredText: Required items: 3 optional
label: Notify me by optional: true optionalText: Optional items: 3 required-asterisk
value: ["email"] label: Notify me by required: true requiredMark: asterisk requiredText: Required items: 3 required-error
label: Notify me by supportingText: Choose all that apply. errorText: Choose at least one way to reach you. error: true required: true requiredText: Required items: 3 horizontal
value: ["email"] label: Notify me by orientation: horizontal items: 3 horizontal-many
value: ["mon","tue","wed","thu","fri"] label: Days supportingText: The days the report is sent. orientation: horizontal items: 10 disabled
value: ["email"] label: Notify me by supportingText: Choose all that apply. disabled: true required: true requiredText: Required items: 3 one-disabled
value: ["email"] label: Notify me by items: 3 long-label
label: Which of these ways may we use to reach… supportingText: Choose all that apply. We use the first… errorText: Choose at least one way to reach you, or… error: true required: true requiredText: Required items: 3 required-ja
value: ["email"] label: 連絡方法 supportingText: 当てはまるものをすべて選んでください。 required: true requiredText: 必須 items: 3 rtl
value: ["email"] label: طرق الإشعار supportingText: اختر كل ما ينطبق. required: true requiredText: مطلوب direction: rtl items: 3 horizontal-rtl
label: ימים errorText: יש לבחור יום אחד לפחות. error: true orientation: horizontal direction: rtl items: 3 ltr-label-rtl
value: ["2.4","5"] label: Wi-Fi 6E supportingText: 2.4 GHz, 5 GHz, 6 GHz direction: rtl items: 3 error-selected
value: ["text"] label: Notify me by supportingText: Choose all that apply. errorText: This number cannot receive text messages… error: true required: true requiredText: Required items: 3 select-all-none
label: Notify me by selectAllLabel: All of these items: 3 select-all-some
value: ["email"] label: Notify me by selectAllLabel: All of these items: 3 select-all-all
value: ["email","text","push"] label: Notify me by selectAllLabel: All of these items: 3 select-all-one-disabled
value: ["email","push"] label: Notify me by selectAllLabel: All of these items: 3 生成自 specs/checkbox-group.spec.json, tokens/comp/field-group.tokens.json, design/tokens/
- 这个元素的 shadow root 里有一个
<tanrei-field-group>,它包着全选复选框(给出select-all-label时才有)和一个插槽。复选框仍然是你自己的子元素。导入这个元素,两个成员也随之完成定义。 value只是属性(property):一个数组,写入和读出时都会复制。它没有value属性(attribute),因为把列表写进属性(attribute)需要一个分隔符,而值里可能就含有这个分隔符;HTML 也已经有了现成的写法:每个子元素上的checked。- 既没有
value属性(attribute)、也没有设置值的复选框就没有值:单独的复选框提交的那个“on”,在组里不会被读取。它画成未选中,调试构建会报告它。 - 这个元素是与表单关联的元素。它接受
name和form,并在name之下为每个值提交一个条目——与同名的原生复选框提交的内容相同。它的复选框无论带有什么name,都不提交任何东西。它重置为最初的值,跟随被禁用的<fieldset>,并在 设置了required而列表为空时报告缺少值。报告用的消息是error-text,没有时是必填文字,再没有时是label:请从一开始就给出error-text,而不是只在设置error时才给。 change是 CustomEvent,它的detail.value是列表的一份副本;input在它之前触发。写在组里某个复选框上的监听器,两者都收不到。- 部件:
field-group是框架,它的部件在这里的名称与下一层相同:label、requirement-label、supporting-text、error-text。select-all是全选复选框,它的部件是select-all-container、select-all-box、select-all-mark和select-all-label。你自己的复选框,样式的写法与在任何地方一样,用tanrei-checkbox::part(…)。 - 这个元素本身不能获得焦点。
focus()会把焦点交给第一个启用的复选框,页面自己的<label for>也会把焦点移到那里。 - 这个元素没有角色。用
label为它命名,标签绘制在别处时再加上label-hidden;绝不要用aria-label,它在这里不为任何东西命名。 - 属性(attribute)用 kebab-case 书写:
label-hidden、supporting-text、error-text、required-mark、required-text、optional-text、select-all-label。 - 在元素完成定义之前,以及没有脚本时,它自己的东西一样都不绘制:复选框显示为普通内容。必须在没有脚本时也能工作的页面,写一个原生的
<fieldset>,里面放name相同的原生复选框,它提交的是同样的条目。
- 把标签写成问题,把每个复选框的标签写成对它的一个回答。
- 在
supportingText里说明怎样回答:“可多选。” - 优先排成一列。只有少数几个简短的标签时,才排成一行。
- 只要这一组是必填的,就给出
errorText,这样表单自己的校验就会用你的话来说明。 - 六个或更多选项的组、筛选条件和成组的权限,使用全选复选框。
- 不要把复选框组用于少量选项中只有一个答案的问题:那是单选组。
- 不要为了提交复选框而给它们
name:组会在自己的名称之下提交列表。 - 不要在里面放任何可以操作、却不是复选框的东西。调试构建会报告它,它也不会给值添加任何条目。
- 不要为了表现层级,把这一组缩进到全选复选框之下。选项里面还有选项,那是树。
- 不要用
aria-label为组命名。不该绘制标签的地方,给出label和labelHidden。
- 角色
group- 标注
- label names the group, drawn or not (labelHidden). Each checkbox keeps its own name, its own label beside it; the select-all checkbox is named by selectAllLabel. The group's description is the field group's: supportingText, then errorText while error is true, then requiredText while the group is required, then optionalText while it is optional (FGR-11). A group has no required and no invalid state: the requirement is said in the product's words, and each checkbox of a group in error is exposed as invalid and described by errorText. How many are checked is not said unless the product writes it in supportingText.
- 最小目标
- 即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px
键盘操作
| 按键 | 动作 |
|---|---|
| Tab / Shift+Tab | Moves focus to each enabled checkbox of the set in the order given — the select-all checkbox first where there is one — and out of the group. Every checkbox is a stop; the group is not (CKG-5). |
| Space | Switches the focused checkbox (CHK-1); on the select-all checkbox, checks every enabled checkbox of the set, or unchecks them all where all were checked (CKG-13). |
| Arrow keys | Nothing: a set of checkboxes has no arrow keys on any platform, because moving among them must not change them and each is a stop already. |
| Enter | Nothing on the web, where it submits the form; switches the focused checkbox where the platform activates any focused control with Enter (Android, tvOS), as CHK-1 says. |
备注
- Not a target itself: the group takes no press and is not a focus stop (CKG-5), so minimumTarget holds through its checkboxes, each of which keeps a whole touch target of its own at the distance the field group puts between them (CHK-7, FGR-3).
- The checkbox group is a field group of checkboxes that owns their value (composed: the field group frames it, its checkboxes hold its value). What it adds to the two is what every product otherwise writes: the list of checked values and one change for it, one name in a form with reset and the form's own check that a required set is not empty, and the select-all checkbox's three states. It adds no look and has no tokens of its own.
- One level down is always open: a field group around checkboxes, each checked by the product, is the same picture, the same group, the same names, descriptions and states. Take it where each checkbox must have a name of its own in the form, where the set holds something that is not a checkbox, or where the product wants its own rule for what "all" means.
- Beside the radio group. A radio group has one answer, so it is one stop in the focus order, its arrow keys move and select, and its role has a required and an invalid state. A checkbox group has a list: every checkbox is a stop and Space switches it, there are no arrow keys, and its role is the plain group's — the requirement is said in words and each checkbox says invalid for itself. A radio group in error leaves its rings as they are; a checkbox group in error marks every box, because a checkbox has an error condition and a radio button has none.
- required means "at least one". A least or a greatest number of choices ("choose up to three") is the product's rule: it reads the length of the list in change, sets error and writes errorText with the number in its own words, or disables what may no longer be chosen. The group knows no number, because it would have no words to say it with.
- The select-all checkbox stands for the checkboxes the group holds and a person can switch, never for choices the product has not given it. It is one more stop before the set. It is not the place for a hierarchy: choices inside choices are a tree.
- A disabled group is not submitted; a checkbox disabled by itself in an enabled group keeps its entry in the value and is submitted with it. A choice that is always on ("Email, required for your account") is therefore one disabled, checked checkbox, and needs no hidden field beside it.
- When a checkbox group, and when something else. Several of a few choices that are all worth seeing at once, in a form or a setting: a checkbox group, of up to about seven. One of a few: a radio group. One thing on or off, on its own: a checkbox, or a switch where it takes effect at once. Several of many, or where there is little room: a list with multiple selection, or a select. Rows of a table chosen for an action: the table's own selection. Things on or off that are not one answer — each with a name of its own in the form — or a set of switches: a field group.
- On the web the group's shadow root holds the field group's own element around a slot, and the checkboxes stay the product's children: the group is the field group's `fieldset` with its `legend`, exposed once.
- Heights are the texts' and the checkboxes': 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.
生成自 specs/checkbox-group.spec.json, tokens/sys/measure.tokens.json
- 它是一个组,也就是字段组的那一个,里面包含它的复选框。它的名称是
label,无论标签是否绘制;它的描述是辅助文本、处于错误状态时的错误文本,以及用产品的文字说明的必填或选填。 - 组没有必填状态,也没有无效状态。必填与否用文字说明;组处于错误状态时,其中的每个复选框都暴露为无效,并以错误文本作为描述。
- 每个复选框保留它自己的角色、名称和选中状态。只有一部分选中时,全选复选框说的是“混合”。没有哪个复选框带有在一组中的位置,选中了几个也不朗读。
- 每个启用的复选框各是一个 Tab 停靠点,全选复选框排在最前。Space 切换获得焦点的那一个。没有方向键操作,Enter 留给表单。
- 按下标签、必填和选填标记、描述或错误文本,不会选中任何东西,也不移动焦点。
- 焦点在其中一个复选框上时出现的错误,由字段组以礼貌(polite)的方式朗读一次。
- 方向、放大的文本和强制颜色模式,由各个成员各自应对:在从右到左的布局中,框架和每个复选框都会镜像;文本放大到 200% 时,每段文本都换行,一行变成一列,没有任何内容横向滚动。
- 它没有自己的文字。
requiredText和optionalText留空时,由字段组从产品的文字里读取。
Web 上的已知差距:屏幕阅读器对这个组会说什么,是从各浏览器的无障碍树里读出来的,还没有用屏幕阅读器实际听过。Chromium 在它的无障碍树里给这个元素一个自己的节点,因为表单会校验它:这是一个没有角色、没有名称的节点,包在组的外面。
- 始终给出
label,并给每个复选框一个label。 - 组必填或选填的地方,给出
requiredText和optionalText——写在组上,或者为整个产品只给一次:组没有必填状态,所以要靠这些文字来说明。 - 在
errorText里说明哪里不对,以及怎样改正。颜色绝不是唯一的提示。 - 选了几个很要紧的地方(“已选 2 项,共 5 项”),把它写进
supportingText:否则它不会被朗读。