- Web
tanrei-checkboxschedule - SwiftUI
TanreiCheckboxschedule - Jetpack Compose
TanreiCheckboxschedule - Flutter
TanreiCheckboxschedule - React Native
Checkboxschedule
生成自 docs/status/status.json, specs/checkbox.spec.json
未选中、选中和半选
让复选框采用现在的尺寸和图标集图标的那次修订,只改变了它的外观和动效,所以下面的示例全部适用于当前版本。Web 按规格绘制它并为它添加动画。SwiftUI、Compose、Flutter 和 React Native 待跟进:它们已经采用新的尺寸,但在跟上之前,仍然用各自的标记绘制对勾和横线,并以各自的方式做动画。此后还有两处改进,目前只在 Web 上实现:对勾和横线有了自己的、更粗的字重;方框的轮廓和填充作为一个形状来绘制。
<tanrei-checkbox label="通过邮件接收更新"></tanrei-checkbox>
<tanrei-checkbox checked label="通过邮件接收更新"></tanrei-checkbox>
<!-- 子项选中情况不一致的父级。激活它会将它选中,并清除 indeterminate。 -->
<tanrei-checkbox indeterminate label="全选"></tanrei-checkbox>
<!-- 在表单中,它在选中时以 name 提交 value(默认为 "on"),并遵守 required。 -->
<form>
<tanrei-checkbox name="terms" required label="我同意服务条款"></tanrei-checkbox>
</form>
<script type="module">
import '@tanrei/web/components/checkbox';
// 元素先切换自己的 checked,再触发 change。
document.querySelector('tanrei-checkbox').addEventListener('change', (e) => save(e.detail.checked));
const terms = document.querySelector('[name="terms"]');
terms.error = submitted && !terms.checked;
</script>import { Checkbox } from '@tanrei/react';
<Checkbox checked={updates} label="通过邮件接收更新" onChange={(e) => setUpdates(e.detail.checked)} />
<Checkbox checked={all} indeterminate={some && !all} label="全选" onChange={(e) => selectAll(e.detail.checked)} />
<Checkbox checked={terms} error={submitted && !terms} label="我同意服务条款" onChange={(e) => setTerms(e.detail.checked)} />import Tanrei
TanreiCheckbox("通过邮件接收更新", isOn: $updates)
// isIndeterminate 来自你的数据。激活它会把 isOn 设为 true,随后由你的数据清除它。
TanreiCheckbox("全选", isOn: $allSelected, isIndeterminate: someSelected)import androidx.compose.ui.state.ToggleableState
import io.github.chnotchy.tanrei.components.TanreiCheckbox
TanreiCheckbox(checked = updates, onCheckedChange = { updates = it }, label = "通过邮件接收更新")
// 可能只有部分打开的父级接收一个 ToggleableState。下一个状态由调用方计算:
// 激活半选的复选框会使它变为 On。
TanreiCheckbox(
state = allState,
onClick = { selectAll(allState != ToggleableState.On) },
label = "全选",
)
TanreiCheckbox(
checked = terms,
onCheckedChange = { terms = it },
label = "我同意服务条款",
isError = submitted && !terms,
)import 'package:tanrei/tanrei.dart';
TanreiCheckbox(
value: updates,
onChanged: (bool value) => setState(() => updates = value), // 传 null 则禁用
label: '通过邮件接收更新',
);
// 启用 tristate 时,value 为 null 表示半选。激活它会调用 onChanged(true)。
TanreiCheckbox(
value: allSelected, // bool?:只有部分被选中时为 null
tristate: true,
onChanged: (bool value) => setState(() => selectAll(value)),
label: '全选',
);
TanreiCheckbox(
value: terms,
onChanged: (bool value) => setState(() => terms = value),
isError: submitted && !terms,
label: '我同意服务条款',
);import { Checkbox } from '@tanrei/react-native';
<Checkbox value={updates} onValueChange={setUpdates} label="通过邮件接收更新" />
<Checkbox value={all} indeterminate={some && !all} onValueChange={selectAll} label="全选" />
<Checkbox value={terms} onValueChange={setTerms} error={submitted && !terms} label="我同意服务条款" />选中情况、错误和禁用
在默认系统中,复选框的方框为 18,轮廓为 2,圆角为 2,位于直径 40 的圆形状态层之上,目标为 48。带标签时高 40。对勾和横线是系统图标集里的 check 和 remove 两个图标,以 20 的大小绘制在方框之上。它们有自己的字重,比系统的其他图标更粗:在默认系统中是 500,而图标是 300。在这个大小下,按图标的字重画出的笔画只有大约 1 像素宽,很难看清,所以标记加粗了,大小则保持不变。预设沿用它自己的图标字重。复选框不绘制自己的标记,因此设计系统更换图标集时,这两个图标会和其他图标一起更换。方框的轮廓和填充是一个形状:无论缩放比例或显示密度如何,方框背后的东西都不会从两者之间透出来,选中的方框从边缘到标记是一片均匀的颜色。在 cupertino 下,复选框保留较小的方框和 16 的图标。
复选框发生变化时,方框、轮廓和图标都会改变颜色。出现的图标一边淡入,一边从一半大小开始放大,略微过冲后稳定下来。消失的图标以同样的方式淡出并缩小;在选中与半选之间切换时,对勾消失,横线同时出现。动的只有图标:方框的大小和位置始终不变。首次绘制的复选框会立即显示它的状态,所以页面加载时已经选中的方框不会播放动画。在减弱动态效果时,新的状态会在下一帧完整画出。
构成
构成
unselected 之上。| 部件 | 说明 |
|---|---|
1 container | The round state layer behind the box (Material Design 3's 40). It carries hover, focus and press and is invisible at rest. |
2 box | The small square that shows the condition (Material Design 3's 18, with a 2 corner): empty with an outline when unchecked, filled when checked or indeterminate. |
3 outline | The border of the box, drawn inside its edge. The fill sits inside the border, so a translucent fill and border never overlap; where the two meet they are drawn as one shape, so nothing behind the box shows between them. |
4 mark | The glyph check (checked) or the glyph remove (indeterminate) of the system's icon set, centered on the box and drawn with the icon set's family, fill and optical size at the mark's own weight, comp.checkbox.mark.weight: in the default system heavier than the system's icons, because the mark is a thin stroke on a small filled box and is what tells checked from indeterminate. The checkbox draws no mark of its own; a system that wants another check chooses another icon set. The glyph is decorative: the condition is announced from the state. |
5 label · 可选 | The visible text that names the choice. A click on it toggles the checkbox. |
6 focus-ring | Gap-then-ring indicator shown for keyboard focus, following the box's shape. |
尺寸
尺寸
| 长度 | 令牌 | 值(px) |
|---|---|---|
container size | tanrei.comp.checkbox.container.size | 40 |
box size | tanrei.comp.checkbox.box.size | 18 |
mark size | tanrei.comp.checkbox.mark.size | 20 |
unselected 之上。属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
checked | boolean | false | Whether the choice is on. |
indeterminate | boolean | false | Shows the choice as partly on (a parent whose children are mixed). Wins over checked for what is drawn and announced; activation clears it. |
error | boolean | false | Marks the choice as invalid (a required choice left unmade). Draws the error form of the condition and is exposed as invalid; the message itself belongs to the surrounding form. |
disabled | boolean | false | Prevents interaction and removes the checkbox from the focus order. |
readOnly | boolean | false | Shows the choice and keeps it: the checkbox can be focused and read and is submitted with its form, but the person cannot switch it. Drawn as an enabled checkbox, without the hover and press answers (CHK-24). |
label | string | "" | The text that names the choice: drawn next to the box and used as the accessible name. |
labelHidden | boolean | false | Draws no visible label but keeps label as the accessible name (a row-selection checkbox in a table whose row already says what it is). |
事件
| 事件 | 说明 |
|---|---|
change | The user switched the checkbox; carries the new value of checked (indeterminate is then false). |
状态
行为
以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。
| ID | 需求 |
|---|---|
CHK-1 | WHEN the user activates the checkbox by pointer, touch, Space, assistive technology or a click on its label THE checkbox SHALL switch checked and emit change exactly once with the new value; Enter SHALL activate it only on a platform whose convention activates any focused control with Enter (Android, tvOS), and SHALL NOT on the web, where Enter submits the form. |
CHK-2 | WHEN the user activates the checkbox while indeterminate is true THE checkbox SHALL set checked to true, clear indeterminate, and emit change with true. |
CHK-3 | WHILE disabled is true THE checkbox SHALL NOT change or emit change, SHALL NOT be focusable, SHALL ignore clicks on its label and SHALL expose the disabled state to assistive technology. |
CHK-4 | THE checkbox SHALL draw the condition its props select: indeterminate when indeterminate is true, otherwise selected when checked is true, otherwise unselected; and the error- form of that condition while error is true. |
CHK-5 | WHILE hovered, keyboard-focused or pressed THE checkbox SHALL render its container, box, mark, outline, label and opacity from the component tokens of its condition in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. A part the interaction states recolor changes over the component's state motion, as the container does; only a change of condition uses the selection motion. |
CHK-6 | WHEN the checkbox receives keyboard focus THE checkbox SHALL show the focus ring around its box without animation; focus gained by pointer or touch SHALL NOT show it. |
CHK-7 | WHERE the visual size is below the minimum target THE checkbox SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch. The visible label SHALL be part of the hit area as well. |
CHK-8 | WHILE disabled is true THE checkbox SHALL render its container, box, mark, outline, label and opacity from the disabled component tokens of its condition. |
CHK-9 | WHEN the checkbox is pressed by pointer, touch or Space, or by Enter where Enter activates it (Android, tvOS; never on the web, where Enter submits the form) THE checkbox SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner. |
CHK-10 | WHILE pressed THE checkbox SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation; a press outside the container (on the label) grows it from the container's center); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; box, mark, outline, label, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.checkbox.motion.feedback-duration with motion.state-easing, fades over motion.state-duration with motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced. |
CHK-11 | THE checkbox SHALL ease its container and box corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
CHK-12 | THE checkbox SHALL draw a box of side comp.checkbox.box.size with corners comp.checkbox.box.shape, centered in a round container of diameter comp.checkbox.container.size; its outline of width comp.checkbox.outline.width SHALL lie inside the box's edge and its fill inside the outline, never under it. Its mark SHALL be a glyph of the system's icon set, check while selected and remove while indeterminate, and SHALL NOT be drawn while unselected: centered on the box at comp.checkbox.mark.size, with sys.icon.family.selected, sys.icon.fill.selected, the weight comp.checkbox.mark.weight (not sys.icon.weight) and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, not clipped by the box, and hidden from assistive technology. THE checkbox SHALL draw no mark of its own (no path, shape or character of its own for the check or the dash). The weight SHALL change the stroke of the glyph only: the glyph box stays comp.checkbox.mark.size, and the box, the container and the place of the glyph are the same at every weight. |
CHK-13 | WHEN the condition changes after the checkbox was first drawn THE checkbox SHALL change the box, outline and mark colors over comp.checkbox.motion.selection-duration with comp.checkbox.motion.selection-easing; a glyph that appears SHALL fade in over the same duration and curve while growing about the box's center from comp.checkbox.motion.mark-enter-scale of its size to its full size over comp.checkbox.motion.mark-scale-duration with comp.checkbox.motion.mark-scale-easing, and a glyph that goes SHALL fade out and shrink to comp.checkbox.motion.mark-enter-scale the same way; between checked and indeterminate the check goes and the dash appears at the same time. A change of error alone SHALL only recolor, and the box and container SHALL NOT change size or move. |
CHK-14 | THE checkbox SHALL expose the role checkbox with the state checked, not checked or mixed (mixed while indeterminate is true), and SHALL expose the invalid state while error is true. |
CHK-15 | WHILE label is not empty and labelHidden is false THE checkbox SHALL draw label at the inline end of the container, comp.checkbox.label.gap away, in comp.checkbox.label.typescale, wrapping rather than truncating, the control centered on the label's first line; right-to-left layouts SHALL mirror the order. |
CHK-16 | WHILE labelHidden is true THE checkbox SHALL draw no label and SHALL still expose label as its accessible name; WHILE label is not empty, or a Field that frames it gives it a label, THE checkbox SHALL expose that label as its accessible name, over a name the product gives it on the element (aria-label or aria-labelledby); WHILE label is empty and the product gives the checkbox a name of its own on the element — aria-label, or aria-labelledby naming an element — THE checkbox SHALL expose that name as its accessible name; WHILE label is empty and the product gives it none THE checkbox SHALL still render, and in debug builds report that it has no accessible name. |
CHK-17 | WHERE motion is reduced THE checkbox SHALL draw the new condition whole in the first frame after the change: the box, outline and mark colors of the new condition, the new glyph at its full size and full opacity, and the old glyph gone, with no frame in between; a change that arrives while a selection motion is running SHALL end that motion in the same frame. The change SHALL be neither delayed nor skipped. |
CHK-18 | WHEN the condition changes again before the selection motion of CHK-13 has ended THE checkbox SHALL move from what it draws at that moment to the latest condition, never first jumping to the end of the earlier change, and SHALL end on the latest condition's colors and glyph alone. |
CHK-19 | IF the icon set cannot draw the glyph yet (its font is still loading or failed to load) THE checkbox SHALL draw nothing in the glyph's place, never the glyph's name as text, SHALL still draw the box, its fill and its outline and expose its state, and SHALL show the glyph once the icon set can draw it, without the selection motion. |
CHK-20 | THE checkbox SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the checkbox is shown THE checkbox SHALL draw itself for the new direction at once, without a motion of its own, keeping its selection and its focus. |
CHK-21 | THE checkbox SHALL lay out its label as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE checkbox SHALL still place the label and align its lines by the layout direction, not by the text's own. |
CHK-22 | THE checkbox SHALL draw the outline and the fill of its box as one shape: every point inside the box's outer edge SHALL be painted by the outline, by the fill, or, along the line where the two meet, by both in shares that add up to the whole, so that nothing behind the box shows between them at any zoom, display density or fractional position, in any frame of the selection motion (CHK-13), and so that a box whose outline and fill have the same color is that one color, evenly, from its edge to its mark; the two SHALL NOT be painted over one another either (CHK-12), so that a translucent outline or fill is nowhere darker than its token. |
CHK-23 | WHILE the checkbox stands in a checkbox group THE checkbox SHALL take its checked from the group's value and from the value the group reads from it (CKG-2), SHALL be disabled WHILE the group is (CKG-6), SHALL have its switching by the person reported by the group's change and by no event of its own that a listener on the group would receive as well (CKG-4), and WHERE the platform has forms SHALL submit nothing of its own, whatever name it carries (CKG-8). WHERE the checkbox stands in no checkbox group it SHALL be as it is anywhere — in a field, in a field group, in a table's row or alone: checked is its own, change is its own, and on the web it submits its own entry under its own name. |
CHK-24 | WHILE readOnly is true and disabled is false THE checkbox SHALL stay focusable and in the focus order; SHALL NOT change checked or indeterminate, and SHALL NOT emit change, on any activation — pointer, touch, Space, Enter where Enter activates it, assistive technology, a click on its label or a click dispatched on the element; SHALL expose the read-only state to assistive technology beside its checked, not checked or mixed state; WHERE the platform has forms SHALL be submitted with its form as it is drawn, and SHALL NOT report a missing value to the form's own validation, because the person cannot give one; and SHALL be drawn from the resting component tokens of its condition — the enabled ones, never the disabled ones — and SHALL show no hovered and no pressed state, under any pressed feedback (no overlay, no ripple, no dim), while keyboard focus still selects the focused state and shows the focus ring (CHK-5, CHK-6). The product may still set checked and indeterminate, which then take the selection motion (CHK-13). WHILE disabled is true as well, disabled wins (CHK-3). |
CHK-25 | 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 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 element that carries its role — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the checkbox is invalid — except those the checkbox owns (aria-checked, aria-pressed, aria-disabled), which it SHALL move and not expose. 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 SHALL take it off that element. WHEN the product sets an aria- attribute the checkbox owns THE checkbox SHALL report it in debug builds. |
各平台名称对照
对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 组件 | tanrei-checkbox | TanreiCheckbox | TanreiCheckbox | TanreiCheckbox | Checkbox |
checked · 属性 | checked | isOn | checked | value | value |
indeterminate · 属性 | indeterminate | isIndeterminate | state | tristate | indeterminate |
error · 属性 | error | isError | isError | isError | error |
disabled · 属性 | disabled | disabled | enabled | onChanged | disabled |
readOnly · 属性 | readOnly | readOnly | readOnly | readOnly | readOnly |
label · 属性 | label | title | label | label | label |
labelHidden · 属性 | labelHidden | labelsHidden | labelHidden | labelHidden | labelHidden |
change · 事件 | change | isOn | onCheckedChange | onChanged | onValueChange |
| 备注 | Form-associated (ElementInternals): it takes part in a form with name and value (default "on"), resets with it, and honours required as the native checkbox does; those three are web attributes, not spec props. It switches its own checked before firing change (and input), as a native checkbox does. labelHidden is the label-hidden attribute. Inside a tanrei-checkbox-group (found with closest() when it connects) it takes checked from the group, ignores name and required, submits nothing and fires no input or change of its own (CHK-23); a value attribute it does not carry is no value there, not "on". readOnly is the readonly attribute (HTML's name; the native attribute does not apply to a checkbox, so the element cancels the activation itself and writes aria-readonly on its native checkbox). | Toggle idiom: TanreiCheckbox(_ title:, isOn: Binding<Bool>, isIndeterminate: Bool = false); activation writes the binding, which is the change event. isIndeterminate is a value the caller derives from its data (a select-all is derived state), so activation sets isOn to true and the caller's data clears it. disabled follows .disabled(_:); labelsHidden follows .labelsHidden(). | Two overloads, as Compose's own: TanreiCheckbox(checked, onCheckedChange, …) and, for a parent that can be indeterminate, TanreiCheckbox(state: ToggleableState, onClick, …) in the TriStateCheckbox idiom, where the caller computes the next state (CHK-2 says it is On). enabled is the inverse flag; a null onCheckedChange draws a checkbox that only displays. | Flutter idiom: value is bool?; with tristate: true a null value is indeterminate. Unlike Flutter's Material checkbox, which cycles null → false, activation from null calls onChanged(true) (CHK-2). A null onChanged disables it. | Controlled, in React Native's value / onValueChange idiom: the caller sets value from the callback. |
令牌
| 令牌 | 引用 | 解析值(浅色) |
|---|---|---|
--tanrei-comp-checkbox-box-shape | — | 2px |
--tanrei-comp-checkbox-box-size | — | 18px |
--tanrei-comp-checkbox-container-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-checkbox-container-size | — | 40px |
--tanrei-comp-checkbox-disabled-container-shape | — | 9999px |
--tanrei-comp-checkbox-disabled-opacity | — | 1 |
--tanrei-comp-checkbox-error-indeterminate-box-color | sys.color.error | |
--tanrei-comp-checkbox-error-indeterminate-container-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-box-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-container-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-label-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-mark-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-outline-color | — | |
--tanrei-comp-checkbox-error-indeterminate-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-box-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-container-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-label-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-mark-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-outline-color | — | |
--tanrei-comp-checkbox-error-indeterminate-focused-state-layer-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-box-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-container-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-label-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-mark-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-outline-color | — | |
--tanrei-comp-checkbox-error-indeterminate-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-error-indeterminate-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-error-indeterminate-mark-color | sys.color.on-error | |
--tanrei-comp-checkbox-error-indeterminate-outline-color | sys.color.error | |
--tanrei-comp-checkbox-error-indeterminate-pressed-box-color | — | |
--tanrei-comp-checkbox-error-indeterminate-pressed-container-color | — | |
--tanrei-comp-checkbox-error-indeterminate-pressed-label-color | — | |
--tanrei-comp-checkbox-error-indeterminate-pressed-mark-color | — | |
--tanrei-comp-checkbox-error-indeterminate-pressed-outline-color | — | |
--tanrei-comp-checkbox-error-indeterminate-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-error-indeterminate-state-layer-color | comp.checkbox.error-indeterminate.outline.color | |
--tanrei-comp-checkbox-error-selected-box-color | sys.color.error | |
--tanrei-comp-checkbox-error-selected-container-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-box-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-container-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-label-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-mark-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-outline-color | — | |
--tanrei-comp-checkbox-error-selected-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-error-selected-focused-box-color | — | |
--tanrei-comp-checkbox-error-selected-focused-container-color | — | |
--tanrei-comp-checkbox-error-selected-focused-label-color | — | |
--tanrei-comp-checkbox-error-selected-focused-mark-color | — | |
--tanrei-comp-checkbox-error-selected-focused-outline-color | — | |
--tanrei-comp-checkbox-error-selected-focused-state-layer-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-box-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-container-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-label-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-mark-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-outline-color | — | |
--tanrei-comp-checkbox-error-selected-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-error-selected-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-error-selected-mark-color | sys.color.on-error | |
--tanrei-comp-checkbox-error-selected-outline-color | sys.color.error | |
--tanrei-comp-checkbox-error-selected-pressed-box-color | — | |
--tanrei-comp-checkbox-error-selected-pressed-container-color | — | |
--tanrei-comp-checkbox-error-selected-pressed-label-color | — | |
--tanrei-comp-checkbox-error-selected-pressed-mark-color | — | |
--tanrei-comp-checkbox-error-selected-pressed-outline-color | — | |
--tanrei-comp-checkbox-error-selected-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-error-selected-state-layer-color | comp.checkbox.error-selected.outline.color | |
--tanrei-comp-checkbox-error-unselected-box-color | — | |
--tanrei-comp-checkbox-error-unselected-container-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-box-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-container-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-label-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-mark-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-outline-color | — | |
--tanrei-comp-checkbox-error-unselected-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-box-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-container-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-label-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-mark-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-outline-color | — | |
--tanrei-comp-checkbox-error-unselected-focused-state-layer-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-box-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-container-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-label-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-mark-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-outline-color | — | |
--tanrei-comp-checkbox-error-unselected-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-error-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-error-unselected-mark-color | — | |
--tanrei-comp-checkbox-error-unselected-outline-color | sys.color.error | |
--tanrei-comp-checkbox-error-unselected-pressed-box-color | — | |
--tanrei-comp-checkbox-error-unselected-pressed-container-color | — | |
--tanrei-comp-checkbox-error-unselected-pressed-label-color | — | |
--tanrei-comp-checkbox-error-unselected-pressed-mark-color | — | |
--tanrei-comp-checkbox-error-unselected-pressed-outline-color | — | |
--tanrei-comp-checkbox-error-unselected-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-error-unselected-state-layer-color | comp.checkbox.error-unselected.outline.color | |
--tanrei-comp-checkbox-focused-container-shape | — | 9999px |
--tanrei-comp-checkbox-focused-opacity | — | 1 |
--tanrei-comp-checkbox-hovered-container-shape | — | 9999px |
--tanrei-comp-checkbox-hovered-opacity | — | 1 |
--tanrei-comp-checkbox-indeterminate-box-color | sys.color.primary | |
--tanrei-comp-checkbox-indeterminate-container-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-box-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-container-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-label-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-mark-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-outline-color | — | |
--tanrei-comp-checkbox-indeterminate-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-box-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-container-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-label-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-mark-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-outline-color | — | |
--tanrei-comp-checkbox-indeterminate-focused-state-layer-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-box-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-container-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-label-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-mark-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-outline-color | — | |
--tanrei-comp-checkbox-indeterminate-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-indeterminate-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-indeterminate-mark-color | sys.color.on-primary | |
--tanrei-comp-checkbox-indeterminate-outline-color | sys.color.primary | |
--tanrei-comp-checkbox-indeterminate-pressed-box-color | — | |
--tanrei-comp-checkbox-indeterminate-pressed-container-color | — | |
--tanrei-comp-checkbox-indeterminate-pressed-label-color | — | |
--tanrei-comp-checkbox-indeterminate-pressed-mark-color | — | |
--tanrei-comp-checkbox-indeterminate-pressed-outline-color | — | |
--tanrei-comp-checkbox-indeterminate-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-indeterminate-state-layer-color | comp.checkbox.indeterminate.outline.color | |
--tanrei-comp-checkbox-label-gap | sys.space.space50 | 4px |
--tanrei-comp-checkbox-mark-size | sys.icon.small | 20px |
--tanrei-comp-checkbox-mark-weight | sys.icon.mark.weight | 500 |
--tanrei-comp-checkbox-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-checkbox-motion-mark-enter-scale | — | 0.5 |
--tanrei-comp-checkbox-motion-mark-scale-duration | sys.motion.role.selection.mark | 250ms |
--tanrei-comp-checkbox-motion-mark-scale-easing | sys.motion.easing.spatial | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-checkbox-motion-selection-duration | sys.motion.role.selection.mark | 250ms |
--tanrei-comp-checkbox-motion-selection-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-checkbox-motion-state-duration | sys.motion.role.state.compact | 200ms |
--tanrei-comp-checkbox-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-checkbox-outline-width | sys.border.thick | 2px |
--tanrei-comp-checkbox-pressed-container-shape | — | 9999px |
--tanrei-comp-checkbox-pressed-opacity | — | 1 |
--tanrei-comp-checkbox-selected-box-color | sys.color.primary | |
--tanrei-comp-checkbox-selected-container-color | — | |
--tanrei-comp-checkbox-selected-disabled-box-color | — | |
--tanrei-comp-checkbox-selected-disabled-container-color | — | |
--tanrei-comp-checkbox-selected-disabled-label-color | — | |
--tanrei-comp-checkbox-selected-disabled-mark-color | — | |
--tanrei-comp-checkbox-selected-disabled-outline-color | — | |
--tanrei-comp-checkbox-selected-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-selected-focused-box-color | — | |
--tanrei-comp-checkbox-selected-focused-container-color | — | |
--tanrei-comp-checkbox-selected-focused-label-color | — | |
--tanrei-comp-checkbox-selected-focused-mark-color | — | |
--tanrei-comp-checkbox-selected-focused-outline-color | — | |
--tanrei-comp-checkbox-selected-focused-state-layer-color | — | |
--tanrei-comp-checkbox-selected-hovered-box-color | — | |
--tanrei-comp-checkbox-selected-hovered-container-color | — | |
--tanrei-comp-checkbox-selected-hovered-label-color | — | |
--tanrei-comp-checkbox-selected-hovered-mark-color | — | |
--tanrei-comp-checkbox-selected-hovered-outline-color | — | |
--tanrei-comp-checkbox-selected-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-selected-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-selected-mark-color | sys.color.on-primary | |
--tanrei-comp-checkbox-selected-outline-color | sys.color.primary | |
--tanrei-comp-checkbox-selected-pressed-box-color | — | |
--tanrei-comp-checkbox-selected-pressed-container-color | — | |
--tanrei-comp-checkbox-selected-pressed-label-color | — | |
--tanrei-comp-checkbox-selected-pressed-mark-color | — | |
--tanrei-comp-checkbox-selected-pressed-outline-color | — | |
--tanrei-comp-checkbox-selected-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-selected-state-layer-color | comp.checkbox.selected.outline.color | |
--tanrei-comp-checkbox-unselected-box-color | — | |
--tanrei-comp-checkbox-unselected-container-color | — | |
--tanrei-comp-checkbox-unselected-disabled-box-color | — | |
--tanrei-comp-checkbox-unselected-disabled-container-color | — | |
--tanrei-comp-checkbox-unselected-disabled-label-color | — | |
--tanrei-comp-checkbox-unselected-disabled-mark-color | — | |
--tanrei-comp-checkbox-unselected-disabled-outline-color | — | |
--tanrei-comp-checkbox-unselected-disabled-state-layer-color | — | |
--tanrei-comp-checkbox-unselected-focused-box-color | — | |
--tanrei-comp-checkbox-unselected-focused-container-color | — | |
--tanrei-comp-checkbox-unselected-focused-label-color | — | |
--tanrei-comp-checkbox-unselected-focused-mark-color | — | |
--tanrei-comp-checkbox-unselected-focused-outline-color | — | |
--tanrei-comp-checkbox-unselected-focused-state-layer-color | — | |
--tanrei-comp-checkbox-unselected-hovered-box-color | — | |
--tanrei-comp-checkbox-unselected-hovered-container-color | — | |
--tanrei-comp-checkbox-unselected-hovered-label-color | — | |
--tanrei-comp-checkbox-unselected-hovered-mark-color | — | |
--tanrei-comp-checkbox-unselected-hovered-outline-color | — | |
--tanrei-comp-checkbox-unselected-hovered-state-layer-color | — | |
--tanrei-comp-checkbox-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-checkbox-unselected-mark-color | — | |
--tanrei-comp-checkbox-unselected-outline-color | sys.color.outline | |
--tanrei-comp-checkbox-unselected-pressed-box-color | — | |
--tanrei-comp-checkbox-unselected-pressed-container-color | — | |
--tanrei-comp-checkbox-unselected-pressed-label-color | — | |
--tanrei-comp-checkbox-unselected-pressed-mark-color | — | |
--tanrei-comp-checkbox-unselected-pressed-outline-color | — | |
--tanrei-comp-checkbox-unselected-pressed-state-layer-color | — | |
--tanrei-comp-checkbox-unselected-state-layer-color | comp.checkbox.unselected.outline.color | |
--tanrei-comp-checkbox-label-typescale | sys.typescale.body | 400 14px/1.5714 |
测试样例
作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。
unselected
label: Email me updates selected
checked: true label: Email me updates indeterminate
indeterminate: true label: Select all error-unselected
error: true label: I accept the terms error-selected
checked: true error: true label: I accept the terms error-indeterminate
indeterminate: true error: true label: Select all disabled-unselected
disabled: true label: Email me updates disabled-selected
checked: true disabled: true label: Email me updates disabled-indeterminate
indeterminate: true disabled: true label: Select all label-hidden
checked: true label: Select row labelHidden: true read-only-selected
checked: true readOnly: true label: Send me a receipt 生成自 specs/checkbox.spec.json, tokens/comp/checkbox.tokens.json, design/tokens/
复选框用于彼此独立的选项:用户可以任选若干项(也可以一项都不选)的列表,或者像同意条款这样单独的“是或否”。选择在用户确认时生效,例如提交表单或保存。复选框也用来选择表格中的行或列表中的项。
半选状态属于子项只有部分被选中的父级复选框,例如列表上方的“全选”。它不是用户可以选择的第三种答案。
只读复选框显示一个可以阅读但不能更改的答案,例如提交订单前的确认步骤,或只有部分人可以更改的设置。它留在焦点顺序中,会连同状态一起被读作只读,并随表单提交。它看起来与启用的复选框相同,只是没有悬停和按下的反馈,这样它读起来是一个答案,而不是不可用的东西。选项根本不适用时,请使用 disabled;没有人需要到达它时,请把答案写成文字。
| 控件 | 选项 | 何时生效 | 用途 |
|---|---|---|---|
| 复选框 | 任意数量的独立选项,也可以一项都不选 | 用户确认时 | 表单中的选项、同意条款、选择行 |
| 单选按钮 | 从全部显示出来的少量选项中恰好选一个 | 通常在用户确认时 | 一个问题的一个答案 |
| 开关 | 一项设置,开或关 | 立即 | 一经更改就生效的设置 |
- 把标签写成选项本身,并用肯定的说法:写“通过邮件接收更新”,而不是“不要给我发邮件”。
- 一组复选框纵向排列,每行一个,让每个标签都从同一位置开始。
- 半选状态由子项推导得出。父级被激活时,选中所有子项。
- 必选的复选框没有选中时,设置
error,并在表单的消息中说明原因。
- 不要把复选框用于立即生效的更改。请使用开关。
- 不要把复选框用于互斥的选项。请使用单选按钮。
- 不要在标签里放链接或其他控件。点击标签会切换复选框。
- 不要隐藏标签,除非复选框所在的行已经说明了它选择的是什么。
- 角色
checkbox- 标注
- label is the accessible name, visible or not (labelHidden). A click on the visible label toggles the checkbox, as a native label does. A checkbox with no label is reported in debug builds.
- 最小目标
- 即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px
键盘操作
| 按键 | 动作 |
|---|---|
| Tab | Moves focus to the checkbox. |
| Space | Switches it (an indeterminate checkbox becomes checked); nothing while it is read-only. |
| Enter | Nothing on the web (it submits the form); switches it where the platform activates any focused control with Enter (Android, tvOS). |
备注
- The mixed state is announced as such (aria-checked="mixed" on the web, a toggleable state of Indeterminate on Android, a Semantics mixed value in Flutter, accessibilityState checked "mixed" in React Native). SwiftUI has no mixed trait on iOS: the value is announced as "mixed" through accessibilityValue.
- error is exposed as invalid (aria-invalid on the web, the error semantics on Android and Flutter). The reason is text the form shows and associates with the checkbox; the checkbox does not draw it.
- Read-only (CHK-24) is a checkbox that shows an 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. HTML's readonly attribute does not apply to a checkbox, so a read-only checkbox is not a native one's: on the web it keeps the native checkbox enabled, cancels its activation and says read-only with aria-readonly, which WAI-ARIA allows on the checkbox role; React Aria's isReadOnly is the same: focusable, announced with its value, not changeable. Where the answer needs no control at all, write it as text; where the person may not reach it at all, it is disabled.
- Several checkboxes that answer one question are a checkbox group: it names the set, holds the list of the checked values under one name and says the set's error (CHK-23). A checkbox stands alone where it is one choice of its own — a consent, a setting — and in a field group where each checkbox of a set has a name of its own.
- The check and the dash are decorative glyphs of the icon set, hidden from assistive technology: where a glyph is written as a ligature (the web), its name ("check", "remove") is never announced or shown as text (CHK-12, CHK-19). Checked and indeterminate differ by the glyph alone, and both are exposed as states (CHK-14).
- The mark is drawn at its own weight (comp.checkbox.mark.weight, CHK-12), heavier than the system's icons in the default system. Its color already has the contrast of content on its fill; what a light weight loses is the stroke, which at this size is about one pixel wide and is drawn in a mix of the mark's color and the fill's. A system that sets a lighter weight should look at the dash in particular: it is one stroke, and it is all that tells indeterminate from checked.
- The selection motion is decoration too: under reduced motion the new condition is drawn whole at once, never later (CHK-17).
- The label wraps at 200% text size; the box does not grow with text, because it is a control, not text.
- 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.spec.json, tokens/sys/measure.tokens.json
- 它暴露复选框角色及其状态(选中、未选中,半选时为混合)、禁用状态,以及设置了
error时的无效状态。React Native 没有无效状态,所以在那里错误会画出来,但不会被朗读。 - 标签就是无障碍名称,无论它是画出来的,还是用
labelHidden隐藏的。画出来的文本对辅助技术隐藏,所以名称只读一次。 - 点击或轻点标签会切换复选框。标签是点击区域的一部分,点击区域满足最小目标。
- Space 切换它。Enter 只在平台用 Enter 激活任何获得焦点的控件时(Android、tvOS)才切换它;在 Web 上,Enter 提交表单。
- 设置
readOnly时,它仍可获得焦点,并在状态之外被读作只读。按下、Space、点击标签或click()都不会改变它,也没有悬停和按下的反馈。在 Web 上它的值照常提交,必填时也不会报告缺少值。HTML 的readonly对复选框无效,所以由组件自己完成这些。 - 它只在键盘聚焦时,围绕方框显示焦点指示。
- 对勾和横线对辅助技术隐藏。选中和混合是根据状态朗读的,绝不根据图标的名称。
- 在减弱动态效果时,新的状态会在变化后的第一帧完整画出,绝不延迟,也不会只画一半。变化过程中再次变化时,从那一刻已经画出的样子继续。
- 图标字体还画不出图标时,复选框在图标的位置上什么都不画,绝不显示“check”这个词,并且仍然绘制填充的方框、暴露它的状态。字体加载完成后,图标就会出现。
- 在 Web 上,它像原生复选框一样参与表单:
name、value、required和重置。 - 缺少标签会在调试构建中报告。
已知的平台差距:
- SwiftUI、Compose、Flutter 和 React Native 仍然用各自的标记绘制对勾和横线,并以各自的方式做动画,没有标记自己的字重,方框也不是一个形状。
readOnly目前只在 Web 上提供:SwiftUI、Compose、Flutter 和 React Native 还没有。- 在
m3-like下,对勾是图标集里的图标,一边淡入一边放大;而 Material 是一笔一笔地画出自己的对勾,并把它弯折成横线(模仿差距checkbox-mark-drawn-on)。
- 加载图标字体。在它加载完成之前,选中的复选框和半选的复选框看起来一样:都是没有图标的填充方框。它们的状态仍然会被朗读。
- 如果你的设计系统为标记设置了更细的字重(
comp.checkbox.mark.weight),请检查横线:它只有一笔,而凭视觉区分半选与选中,靠的只有它。 - 写一个说明选项内容的标签,并且无论复选框是否选中,都保持不变。状态由平台朗读。
- 设置
error时,把原因以文本形式显示在复选框旁边或表单的摘要中,并把它与复选框关联起来。复选框只说明自己无效,不说明原因。在 React Native 上,这条消息是用户听到错误的唯一途径。 - 为回答同一个问题的一组复选框起一个名称:在 Web 上,使用带
<legend>的<fieldset>。