ラジオボタン
一度にすべて見えている少数の選択肢から、ちょうど 1 つを選べるようにします。
- カテゴリ
- 選択
- トークン
comp.radio-button- レベル
- 基礎
- 使用先
- Radio group
- Web
tanrei-radio-buttonschedule - SwiftUI
TanreiRadioButtonschedule - Jetpack Compose
TanreiRadioButtonschedule - Flutter
TanreiRadioButtonschedule - React Native
RadioButtonschedule
生成元 docs/status/status.json, specs/radio-button.spec.json
グループ
ラジオボタンに今の寸法と、太くなって塗りに変わるリングを取り入れた改訂で変わったのは見た目と動きだけなので、以下の例はどれも現在の仕様に沿っています。Web は仕様どおりに描きます。SwiftUI、Compose、Flutter、React Native は追従待ちで、追従するまでは以前のラジオボタン、つまり 28 のステートレイヤーの中に、細い線の 16 のリングと 8 の点を描き、48 のターゲットの高さもレイアウトで確保しません。その後の調整が 1 つ、いまのところ Web だけにあります。リングと塗りを 1 つの形として描くことです。
<!-- 同じ name を持つ選択肢が 1 つのグループになります。グループの名前は、それを囲む要素で付けます。 -->
<div role="radiogroup" aria-labelledby="frequency-label">
<span id="frequency-label">頻度</span>
<tanrei-radio-button name="frequency" value="daily" label="毎日"></tanrei-radio-button>
<tanrei-radio-button name="frequency" value="weekly" checked label="毎週"></tanrei-radio-button>
<tanrei-radio-button name="frequency" value="monthly" label="毎月"></tanrei-radio-button>
</div>
<script type="module">
import '@tanrei/web/components/radio-button';
// change を発火するのは選ばれた選択肢だけです。選択が外れた側は発火しません。
document.querySelector('[role="radiogroup"]').addEventListener('change', (e) => save(e.detail.value));
</script>import { RadioButton } from '@tanrei/react';
<div role="radiogroup" aria-labelledby="frequency-label">
<span id="frequency-label">頻度</span>
{options.map(({ value, label }) => (
<RadioButton
key={value}
name="frequency"
value={value}
label={label}
checked={frequency === value}
onChange={(e) => setFrequency(e.detail.value)}
/>
))}
</div>import Tanrei
// selection は Binding<Frequency?> です。値が value と等しい選択肢が選ばれた状態になります。
TanreiRadioGroup("頻度") {
TanreiRadioButton("毎日", value: Frequency.daily, selection: $frequency)
TanreiRadioButton("毎週", value: Frequency.weekly, selection: $frequency)
TanreiRadioButton("毎月", value: Frequency.monthly, selection: $frequency)
}import androidx.compose.foundation.selection.selectableGroup
import io.github.chnotchy.tanrei.components.TanreiRadioButton
// グループは親が作ります。各選択肢の onClick が自分の値を持ちます。
Column(Modifier.selectableGroup()) {
for ((value, label) in listOf("daily" to "毎日", "weekly" to "毎週", "monthly" to "毎月")) {
TanreiRadioButton(selected = frequency == value, onClick = { frequency = value }, label = label)
}
}import 'package:tanrei/tanrei.dart';
TanreiRadioGroup<Frequency>(
groupValue: frequency,
onChanged: (Frequency value) => setState(() => frequency = value),
semanticLabel: '頻度',
child: Column(
children: <Widget>[
for (final Frequency option in Frequency.values)
TanreiRadioButton<Frequency>(value: option, label: option.label),
],
),
);import { RadioButton, RadioGroup } from '@tanrei/react-native';
<RadioGroup accessibilityLabel="頻度">
{options.map(({ value, label }) => (
<RadioButton key={value} value={value} label={label} selected={frequency === value} onSelect={setFrequency} />
))}
</RadioGroup>選択、未選択、無効状態
既定のシステムでは、ラジオボタンは線の太さ 2 の 20 のリングを、直径 40 の丸いステートレイヤーの上に描きます。選ばれた選択肢に点は描きません。リングが内側へ半径の半分まで太くなり、内側に残った円を、リングの色の 16% の不透明度で塗ります。淡い色合いで、リングとは 3:1 以上の差を保ちます。リングの外径は、どちらの状態でも変わりません。リングと塗りは 1 つの形です。どの拡大率や画面密度でも、また変化の途中のどのフレームでも、両者の間から背後が透けることはありません。選択が変わると、線が太くなるか細くなるのと同時に、リングの色が変わり、塗りがフェードインまたはフェードアウトします。塗りが中心から広がることはありません。動きの途中で再び変わったときは、その時点の姿から引き返します。動きを減らす設定では、新しい状態をすぐに、完全に示します。
各選択肢は、レイアウトの中でターゲットと同じ 48 以上の高さを取り、ステートレイヤーはその中央に置かれます。そのため、隙間なく縦に並べても、選択肢どうしがターゲットを分け合うことはありません。もっと詰めて並べたいプロダクトは、自分のレイヤで option.min-height を下げ、ターゲットが重なることを受け入れます。
m3-like では、選ばれた選択肢は Material のリング、隙間、点をそのまま描きますが、点は中心から拡大するのではなくフェードインします。cupertino では、ラジオボタンは小さめの寸法のままで、Apple の見た目を描きます。選ばれていないときは細いリングの中の明るい円、選ばれているときは明るい中心を囲む色付きの帯です。
構成要素
構成要素
unselected の上に描いています。寸法
寸法
| 長さ | トークン | 値(px) |
|---|---|---|
container size | tanrei.comp.radio-button.container.size | 40 |
ring size | tanrei.comp.radio-button.ring.size | 20 |
unselected の上に描いています。プロパティ
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
checked | boolean | false | Whether this option is the one chosen in its group. Inside a radio group it follows the group's value. |
value | string | "" | What this option stands for: carried by change, and submitted with a form on the web. |
name | string | "" | The group this option belongs to where it does not stand in a radio group: on each platform that platform's bare way of forming a group (see platforms); the options of one group exclude each other and share one tab stop. Ignored inside a radio group, which is the group. |
disabled | boolean | false | Prevents interaction; the option is skipped by the focus order and by the arrow keys. |
readOnly | boolean | false | The person cannot select this option: it can still be focused and read, and the arrow keys still reach it, but a press, Space or an arrow key that lands on it selects nothing. Drawn as an enabled option, without the hover and press answers. A radio group that is read-only makes each of its options so (RAD-28). |
error | boolean | false | Draws the option in the error form of its condition: the ring, and the fill while selected, in the error color (a required group left unanswered, a choice that cannot be taken). A radio group or a field group in error puts every option it holds there, so set it by hand only on options grouped without one (RAD-25). It draws and nothing more: the group, not the option, is exposed as invalid, and the words that say why are the group's. |
label | string | "" | The text that names the option: drawn next to the ring and used as the accessible name. |
labelHidden | boolean | false | Draws no visible label but keeps label as the accessible name. |
イベント
| イベント | 説明 |
|---|---|
change | This option became selected by the user; carries its value. The option it replaced does not emit. Inside a radio group the group emits change in its place (RGP-4). |
状態
振る舞い
EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。
| ID | 要件 |
|---|---|
RAD-1 | WHEN the user activates an unselected radio button by pointer, touch, Space, assistive technology or a click on its label THE radio button SHALL become selected and emit change exactly once with its 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. |
RAD-2 | WHEN the user activates a radio button that is already selected THE radio button SHALL stay selected and SHALL NOT emit change: a radio button is never cleared by its own activation. |
RAD-3 | WHEN a radio button becomes selected THE other radio buttons of its group SHALL become unselected, without emitting change. |
RAD-4 | WHEN a radio button in a group has keyboard focus and the user presses Down Arrow or Right Arrow THE group SHALL move focus to the next enabled radio button, wrapping from the last to the first, and select it; Up Arrow and Left Arrow SHALL do the same towards the previous one; in a right-to-left layout Right Arrow SHALL move to the previous and Left Arrow to the next. |
RAD-5 | THE group SHALL keep exactly one of its radio buttons in the tab order: the selected one, or the first enabled one while none is selected; Tab SHALL leave the group rather than move within it. |
RAD-6 | WHILE disabled is true THE radio button SHALL NOT change or emit change, SHALL NOT be focusable, SHALL be skipped by the arrow keys, SHALL ignore clicks on its label and SHALL expose the disabled state to assistive technology. |
RAD-7 | WHILE hovered, keyboard-focused or pressed THE radio button SHALL render its container, ring, fill, label and opacity from the component tokens of its condition (selected or unselected, or the error- form of either, RAD-26) 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 (RAD-15). |
RAD-8 | WHEN the radio button receives keyboard focus THE radio button SHALL show the focus ring around its ring without animation; focus gained by pointer or touch SHALL NOT show it. |
RAD-9 | WHERE the visual size is below the minimum target THE radio button 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. |
RAD-10 | WHILE disabled is true THE radio button SHALL render its container, ring, fill, label and opacity from the disabled component tokens of its condition. |
RAD-11 | WHEN the radio button 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 radio button 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. |
RAD-12 | WHILE pressed THE radio button 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; ring, fill, 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.radio-button.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. |
RAD-13 | THE radio button SHALL ease its container 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. |
RAD-14 | THE radio button SHALL draw a ring of outer diameter comp.radio-button.ring.size centered in a round container of diameter comp.radio-button.container.size, the outer diameter the same in both conditions; while unselected the ring SHALL be stroked at comp.radio-button.ring.width inside that diameter, and while selected at comp.radio-button.ring.selected-width-share of the ring's radius (1 being a stroke as wide as the radius, a solid disc), or at comp.radio-button.ring.width where that is wider, the stroke growing inward from the outer diameter. It SHALL draw no dot of its own; what is drawn inside the ring is the fill (RAD-19). |
RAD-15 | WHEN the radio button becomes selected THE radio button SHALL, over comp.radio-button.motion.selection-duration with comp.radio-button.motion.selection-easing, thicken the ring's stroke inward from its unselected to its selected width, change the ring color from the unselected to the selected one, and run the fill's color continuously from comp.radio-button.unselected.fill.color (none in the default system) to comp.radio-button.selected.fill.color, fading the opacity in from none rather than passing through any other color; WHEN it becomes unselected it SHALL run the same change in reverse; the fill SHALL NOT grow from or shrink to the center; WHERE motion is reduced it SHALL show the new condition at once and in full, with no intermediate frame. |
RAD-16 | THE radio button SHALL expose the role radio with the state checked or not checked, and its position in its group and the group's size; THE group SHALL be exposed as a radio group: the radio group component, named by its label (RGP-10), or, where the options are grouped without one (RAD-25), a container whose accessible name the product gives. |
RAD-17 | WHILE label is not empty and labelHidden is false THE radio button SHALL draw label at the inline end of the container, comp.radio-button.label.gap away, in comp.radio-button.label.typescale, wrapping rather than truncating, the control centered on the label's first line; right-to-left layouts SHALL mirror the order. |
RAD-18 | WHILE labelHidden is true THE radio button 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 radio button 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 radio button a name of its own on the element — aria-label, or aria-labelledby naming an element — THE radio button SHALL expose that name as its accessible name; WHILE label is empty and the product gives it none THE radio button SHALL still render, and in debug builds report that it has no accessible name. |
RAD-19 | THE radio button SHALL draw the fill in both conditions: the area inside the ring's inner edge at that moment, less comp.radio-button.fill.inset all round, never under the ring, in the fill color of its condition and state (comp.radio-button.selected.fill.color or comp.radio-button.unselected.fill.color at rest, and comp.radio-button.error-selected.fill.color or comp.radio-button.error-unselected.fill.color in the error forms, RAD-26; an unselected one is none in the default system, so an unselected option shows no fill there) at comp.radio-button.fill.opacity; a fill whose diameter would be zero or less SHALL NOT be drawn. |
RAD-20 | WHEN the condition changes again before the selection motion of RAD-15 has finished THE radio button SHALL turn the change back from the stroke width and the ring and fill colors it has reached, and SHALL NOT jump to either end. |
RAD-21 | THE radio button SHALL take at least comp.radio-button.option.min-height of height in layout, the container centered in it; at its default, sys.target.touch, radio buttons stacked with no space between them keep whole targets that do not overlap, and a layer that lowers it accepts shared target area. |
RAD-22 | THE radio button 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 radio button is shown THE radio button SHALL draw itself for the new direction at once, without a motion of its own, keeping its selection and its focus. |
RAD-23 | THE radio button 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 radio button SHALL still place the label and align its lines by the layout direction, not by the text's own. |
RAD-24 | WHILE comp.radio-button.fill.inset is 0 (the default system's) THE radio button SHALL draw the ring and the fill as one shape: every point inside the ring's outer diameter SHALL be painted by the ring, by the fill, or, along the circle where the two meet, by both in shares that add up to the whole, so that nothing behind the radio button shows between the ring's inner edge and the fill at any zoom, display density or fractional position, or in any frame of the selection motion (RAD-15); neither SHALL be painted over the other (RAD-19), so that the fill is nowhere more or less opaque than comp.radio-button.fill.opacity makes it. WHILE comp.radio-button.fill.inset is above 0 the space it leaves between the ring and the fill SHALL show what is behind the radio button, as wide as the inset all round. |
RAD-25 | WHILE the radio button stands in a radio group THE radio button SHALL take its group from it and from nothing else: checked SHALL follow the radio group's value and its own value (RGP-3), name SHALL be ignored, it SHALL be disabled WHILE the radio group is (RGP-7), and its selection by the person SHALL be reported by the radio group's change and by no event of its own that a listener on the group would receive as well (RGP-4). WHERE the radio button stands in no radio group it SHALL form its group by the platform's bare idiom — on the web the radio buttons of one name in the same form, or in the same document or shadow root outside a form — and RAD-3, RAD-4, RAD-5 and RAD-16 SHALL hold among those, the group's name, its required and invalid states and its layout being the product's, or those of a field group whose kind is radio-group that the product put around them (FGR-25). A radio button in no group at all SHALL still render and be selectable, as a group of one, and in debug builds SHALL report it where the platform can tell (on the web, a radio button with neither a radio group nor a name). |
RAD-26 | THE radio button SHALL draw the condition its props select — selected WHILE checked is true, otherwise unselected — and the error- form of that condition (error-selected, error-unselected) WHILE it is in error: WHILE error is true, or WHILE the radio group or the field group it stands in is in error and has put it there (RGP-8, FGR-8), as a checkbox draws the error- form of its condition (CHK-4). In an error form THE radio button SHALL render its container, ring, fill, label and opacity from the component tokens of that form in every state — at rest, hovered, focused and pressed with the precedence of RAD-7, and disabled as RAD-10 says — and SHALL change nothing else: the ring's diameter and its stroke in each condition (RAD-14), the fill's inset and opacity (RAD-19), the label, the focus indication (RAD-8), the hit area (RAD-9) and the selection motion (RAD-15), which runs between the two error forms' colors, are those of the same condition out of error. WHILE disabled is true the disabled tokens of the error form apply, which in the default system are the values of the plain form's: a disabled option shows no error color, because it cannot be the answer that puts the error right. WHEN the radio button enters or leaves the error forms without a change of selection THE radio button SHALL only recolor its ring and its fill, over comp.radio-button.motion.selection-duration with comp.radio-button.motion.selection-easing, and at once WHERE motion is reduced; nothing SHALL change size or move. THE radio button SHALL expose no invalid state and take no description for the error: the radio role has no such state, the group the options form carries it and the words that say why (RAD-16, RGP-10, FGR-25), and an option that repeated them would say the error once for every arrow key. It SHALL never set error itself, and being selected SHALL NOT clear it. WHERE the options are grouped without a radio group or a field group (RAD-25) error is the product's to set, on every option of the group together with the invalid state of the group it built. |
RAD-27 | 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 button SHALL draw each error form exactly as it draws the same condition out of error there — its ring in the platform's own colors, the stroke's weight carrying the selection — and SHALL show no error color, because the person's colors leave none to show: the error is carried by the words of the group's error text and by the group's invalid state (RGP-14, FGR-17). |
RAD-28 | WHILE readOnly is true and disabled is false, or WHILE the radio group it stands in is read-only (RGP-20), THE radio button SHALL stay focusable, keep its place in its group's single tab stop (RAD-5) and be reached by its group's arrow keys (RAD-4); SHALL NOT become selected, and SHALL NOT emit change, on any activation — pointer, touch, Space, Enter where Enter activates it, assistive technology, a click on its label, a click dispatched on the element or an arrow key that moves focus to it — so that an arrow key moves focus to it and selects nothing, and from it moves focus on as RAD-4 says, selecting the option it lands on only where that option is not read-only; 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 (RAD-7, RAD-8). The radio role has no read-only state: the group says it — the radio group (RGP-20), or, where options are grouped without one (RAD-25), the container the product gives them. The product may still set checked. WHILE disabled is true as well, disabled wins (RAD-6). |
RAD-29 | 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 button 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 radio button is invalid — except those the radio button 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 radio button SHALL take it off that element. WHEN the product sets an aria- attribute the radio button owns THE radio button SHALL report it in debug builds. |
プラットフォーム別の名前対応
パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| コンポーネント | tanrei-radio-button | TanreiRadioButton | TanreiRadioButton | TanreiRadioButton | RadioButton |
checked · プロパティ | checked | selection | selected | groupValue | selected |
value · プロパティ | value | value | onClick | value | value |
name · プロパティ | name | TanreiRadioGroup | selectableGroup | TanreiRadioGroup | RadioGroup |
disabled · プロパティ | disabled | disabled | enabled | enabled | disabled |
readOnly · プロパティ | readOnly | readOnly | readOnly | readOnly | readOnly |
error · プロパティ | error | isError | isError | isError | error |
label · プロパティ | label | title | label | label | label |
labelHidden · プロパティ | labelHidden | labelsHidden | labelHidden | labelHidden | labelHidden |
change · イベント | change | selection | onClick | onChanged | onSelect |
| 備考 | Form-associated, with the native radio's idiom: a group is the options with one name under the same form (or the same root without a form); the options manage the arrow keys and the single tab stop among themselves. A tanrei-radio-group is the usual group: inside one the option needs no name, takes checked, its tab stop and its position from the group and submits nothing itself (RAD-25). Options grouped by name alone are wrapped by the product in an element with role radiogroup and a name. labelHidden is the label-hidden attribute. error is a reflected boolean attribute. The option asks the frame around it as a checkbox does (FieldControlController with errorCondition: true, takes: 'all'), and draws its error form while error or the frame's invalid holds (data-error on .root); it writes no aria-invalid, and in a group in error it takes no description from the group's frame — it names itself, and its group says the error (RAD-26); alone in a Field it takes its name and description from the Field as any control does. Under forced-colors: active the error forms are the plain ones (RAD-27). readOnly is the readonly attribute (HTML's name; the native attribute does not apply to a radio button, so the option cancels the activation itself and writes no ARIA state, the radio role having none). In a tanrei-radio-group it is also read-only while the group is, without its own readOnly changing. | TanreiRadioButton(_ title:, value: V, selection: Binding<V?>): checked is selection == value, and activation writes value to the binding, which is the change event. SwiftUI has no radio group on iOS, so TanreiRadioGroup (a container with no tokens) carries the group's name, the arrow keys and the position announcements; it is the radio group component (radio-group), which adds the frame and holds the selection. Lagging: it draws a 16 ring with a 1 stroke and an 8 dot in a 28 state layer, not this spec's ring and dot. The error condition (isError, the error forms of RAD-26 and RAD-27, set by TanreiRadioGroup in error through the environment) is not built on this platform yet. | Compose idiom, as RadioButton: the caller passes selected and an onClick that captures the option's value; the parent forms the group with Modifier.selectableGroup(), which a TanreiRadioGroup (radio-group) applies itself. enabled is the inverse flag. Lagging: it draws a 16 ring with a 1 stroke and an 8 dot in a 28 state layer, not this spec's ring and dot. The error condition (isError, the error forms of RAD-26 and RAD-27, given by a TanreiRadioGroup in error through its composition local) is not built on this platform yet. | Flutter idiom: TanreiRadioButton<T>(value:) inside a TanreiRadioGroup<T>(groupValue:, onChanged:, semanticLabel:) ancestor, shaped like Flutter's RadioGroup (3.35), which the package's minimum Flutter (3.32) lacks and whose name it must not take; checked is groupValue == value. The group carries the arrow keys, the single Tab stop and the radio-group semantics; it is the radio group component (radio-group), which adds the frame. enabled is the inverse flag, as Flutter's radio has since RadioGroup replaced the per-radio onChanged. Lagging: it draws a 16 ring with a 1 stroke and an 8 dot in a 28 state layer, not this spec's ring and dot. The error condition (isError, the error forms of RAD-26 and RAD-27, given by a TanreiRadioGroup in error through its inherited scope) is not built on this platform yet. | Controlled: onSelect(value) fires and the caller moves selected. React Native has no group idiom, so a RadioGroup parent (no tokens) carries the radiogroup role, its name and the arrow keys where a keyboard exists; it is the radio group component (radio-group), which adds the frame and holds the value. Lagging: it draws a 16 ring with a 1 stroke and an 8 dot in a 28 state layer, not this spec's ring and dot. The error condition (error, the error forms of RAD-26 and RAD-27, given by a RadioGroup in error through its context) is not built on this platform yet. |
トークン
| トークン | 参照先 | 解決値(ライト) |
|---|---|---|
--tanrei-comp-radio-button-container-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-radio-button-container-size | — | 40px |
--tanrei-comp-radio-button-disabled-container-shape | — | 9999px |
--tanrei-comp-radio-button-disabled-opacity | — | 1 |
--tanrei-comp-radio-button-error-selected-container-color | — | |
--tanrei-comp-radio-button-error-selected-disabled-container-color | — | |
--tanrei-comp-radio-button-error-selected-disabled-fill-color | — | |
--tanrei-comp-radio-button-error-selected-disabled-label-color | — | |
--tanrei-comp-radio-button-error-selected-disabled-ring-color | — | |
--tanrei-comp-radio-button-error-selected-disabled-state-layer-color | — | |
--tanrei-comp-radio-button-error-selected-fill-color | comp.radio-button.error-selected.ring.color | |
--tanrei-comp-radio-button-error-selected-focused-container-color | — | |
--tanrei-comp-radio-button-error-selected-focused-fill-color | — | |
--tanrei-comp-radio-button-error-selected-focused-label-color | — | |
--tanrei-comp-radio-button-error-selected-focused-ring-color | — | |
--tanrei-comp-radio-button-error-selected-focused-state-layer-color | — | |
--tanrei-comp-radio-button-error-selected-hovered-container-color | — | |
--tanrei-comp-radio-button-error-selected-hovered-fill-color | — | |
--tanrei-comp-radio-button-error-selected-hovered-label-color | — | |
--tanrei-comp-radio-button-error-selected-hovered-ring-color | — | |
--tanrei-comp-radio-button-error-selected-hovered-state-layer-color | — | |
--tanrei-comp-radio-button-error-selected-label-color | sys.color.on-surface | |
--tanrei-comp-radio-button-error-selected-pressed-container-color | — | |
--tanrei-comp-radio-button-error-selected-pressed-fill-color | — | |
--tanrei-comp-radio-button-error-selected-pressed-label-color | — | |
--tanrei-comp-radio-button-error-selected-pressed-ring-color | — | |
--tanrei-comp-radio-button-error-selected-pressed-state-layer-color | — | |
--tanrei-comp-radio-button-error-selected-ring-color | sys.color.error | |
--tanrei-comp-radio-button-error-selected-state-layer-color | comp.radio-button.error-selected.ring.color | |
--tanrei-comp-radio-button-error-unselected-container-color | — | |
--tanrei-comp-radio-button-error-unselected-disabled-container-color | — | |
--tanrei-comp-radio-button-error-unselected-disabled-fill-color | — | |
--tanrei-comp-radio-button-error-unselected-disabled-label-color | — | |
--tanrei-comp-radio-button-error-unselected-disabled-ring-color | — | |
--tanrei-comp-radio-button-error-unselected-disabled-state-layer-color | — | |
--tanrei-comp-radio-button-error-unselected-fill-color | — | |
--tanrei-comp-radio-button-error-unselected-focused-container-color | — | |
--tanrei-comp-radio-button-error-unselected-focused-fill-color | — | |
--tanrei-comp-radio-button-error-unselected-focused-label-color | — | |
--tanrei-comp-radio-button-error-unselected-focused-ring-color | — | |
--tanrei-comp-radio-button-error-unselected-focused-state-layer-color | — | |
--tanrei-comp-radio-button-error-unselected-hovered-container-color | — | |
--tanrei-comp-radio-button-error-unselected-hovered-fill-color | — | |
--tanrei-comp-radio-button-error-unselected-hovered-label-color | — | |
--tanrei-comp-radio-button-error-unselected-hovered-ring-color | — | |
--tanrei-comp-radio-button-error-unselected-hovered-state-layer-color | — | |
--tanrei-comp-radio-button-error-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-radio-button-error-unselected-pressed-container-color | — | |
--tanrei-comp-radio-button-error-unselected-pressed-fill-color | — | |
--tanrei-comp-radio-button-error-unselected-pressed-label-color | — | |
--tanrei-comp-radio-button-error-unselected-pressed-ring-color | — | |
--tanrei-comp-radio-button-error-unselected-pressed-state-layer-color | — | |
--tanrei-comp-radio-button-error-unselected-ring-color | sys.color.error | |
--tanrei-comp-radio-button-error-unselected-state-layer-color | comp.radio-button.error-unselected.ring.color | |
--tanrei-comp-radio-button-fill-inset | — | 0px |
--tanrei-comp-radio-button-fill-opacity | sys.state.selected-state-layer-opacity | 0.16 |
--tanrei-comp-radio-button-focused-container-shape | — | 9999px |
--tanrei-comp-radio-button-focused-opacity | — | 1 |
--tanrei-comp-radio-button-hovered-container-shape | — | 9999px |
--tanrei-comp-radio-button-hovered-opacity | — | 1 |
--tanrei-comp-radio-button-label-gap | sys.space.space50 | 4px |
--tanrei-comp-radio-button-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-radio-button-motion-selection-duration | sys.motion.role.selection.mark | 250ms |
--tanrei-comp-radio-button-motion-selection-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-radio-button-motion-state-duration | sys.motion.role.state.compact | 200ms |
--tanrei-comp-radio-button-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-radio-button-option-min-height | sys.target.touch | 48px |
--tanrei-comp-radio-button-pressed-container-shape | — | 9999px |
--tanrei-comp-radio-button-pressed-opacity | — | 1 |
--tanrei-comp-radio-button-ring-selected-width-share | — | 0.5 |
--tanrei-comp-radio-button-ring-size | — | 20px |
--tanrei-comp-radio-button-ring-width | sys.border.thick | 2px |
--tanrei-comp-radio-button-selected-container-color | — | |
--tanrei-comp-radio-button-selected-disabled-container-color | — | |
--tanrei-comp-radio-button-selected-disabled-fill-color | — | |
--tanrei-comp-radio-button-selected-disabled-label-color | — | |
--tanrei-comp-radio-button-selected-disabled-ring-color | — | |
--tanrei-comp-radio-button-selected-disabled-state-layer-color | — | |
--tanrei-comp-radio-button-selected-fill-color | comp.radio-button.selected.ring.color | |
--tanrei-comp-radio-button-selected-focused-container-color | — | |
--tanrei-comp-radio-button-selected-focused-fill-color | — | |
--tanrei-comp-radio-button-selected-focused-label-color | — | |
--tanrei-comp-radio-button-selected-focused-ring-color | — | |
--tanrei-comp-radio-button-selected-focused-state-layer-color | — | |
--tanrei-comp-radio-button-selected-hovered-container-color | — | |
--tanrei-comp-radio-button-selected-hovered-fill-color | — | |
--tanrei-comp-radio-button-selected-hovered-label-color | — | |
--tanrei-comp-radio-button-selected-hovered-ring-color | — | |
--tanrei-comp-radio-button-selected-hovered-state-layer-color | — | |
--tanrei-comp-radio-button-selected-label-color | sys.color.on-surface | |
--tanrei-comp-radio-button-selected-pressed-container-color | — | |
--tanrei-comp-radio-button-selected-pressed-fill-color | — | |
--tanrei-comp-radio-button-selected-pressed-label-color | — | |
--tanrei-comp-radio-button-selected-pressed-ring-color | — | |
--tanrei-comp-radio-button-selected-pressed-state-layer-color | — | |
--tanrei-comp-radio-button-selected-ring-color | sys.color.primary | |
--tanrei-comp-radio-button-selected-state-layer-color | comp.radio-button.selected.ring.color | |
--tanrei-comp-radio-button-unselected-container-color | — | |
--tanrei-comp-radio-button-unselected-disabled-container-color | — | |
--tanrei-comp-radio-button-unselected-disabled-fill-color | — | |
--tanrei-comp-radio-button-unselected-disabled-label-color | — | |
--tanrei-comp-radio-button-unselected-disabled-ring-color | — | |
--tanrei-comp-radio-button-unselected-disabled-state-layer-color | — | |
--tanrei-comp-radio-button-unselected-fill-color | — | |
--tanrei-comp-radio-button-unselected-focused-container-color | — | |
--tanrei-comp-radio-button-unselected-focused-fill-color | — | |
--tanrei-comp-radio-button-unselected-focused-label-color | — | |
--tanrei-comp-radio-button-unselected-focused-ring-color | — | |
--tanrei-comp-radio-button-unselected-focused-state-layer-color | — | |
--tanrei-comp-radio-button-unselected-hovered-container-color | — | |
--tanrei-comp-radio-button-unselected-hovered-fill-color | — | |
--tanrei-comp-radio-button-unselected-hovered-label-color | — | |
--tanrei-comp-radio-button-unselected-hovered-ring-color | — | |
--tanrei-comp-radio-button-unselected-hovered-state-layer-color | — | |
--tanrei-comp-radio-button-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-radio-button-unselected-pressed-container-color | — | |
--tanrei-comp-radio-button-unselected-pressed-fill-color | — | |
--tanrei-comp-radio-button-unselected-pressed-label-color | — | |
--tanrei-comp-radio-button-unselected-pressed-ring-color | — | |
--tanrei-comp-radio-button-unselected-pressed-state-layer-color | — | |
--tanrei-comp-radio-button-unselected-ring-color | sys.color.outline | |
--tanrei-comp-radio-button-unselected-state-layer-color | comp.radio-button.unselected.ring.color | |
--tanrei-comp-radio-button-label-typescale | sys.typescale.body | 400 14px/1.5714 |
フィクスチャ
基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。
unselected
value: weekly name: frequency label: Weekly selected
checked: true value: weekly name: frequency label: Weekly error-unselected
value: weekly name: frequency error: true label: Weekly error-selected
checked: true value: weekly name: frequency error: true label: Weekly disabled-unselected
value: weekly name: frequency disabled: true label: Weekly disabled-selected
checked: true value: weekly name: frequency disabled: true label: Weekly label-hidden
checked: true value: weekly name: frequency label: Weekly labelHidden: true read-only-selected
checked: true value: weekly name: frequency readOnly: true label: Weekly 生成元 specs/radio-button.spec.json, tokens/comp/radio-button.tokens.json, design/tokens/
答えがちょうど 1 つに決まる問いで、選択肢が少なく、並べて見比べてほしいときに使います。配送の速さや、まとめを送る頻度などです。すべての選択肢が常に見えているぶん、場所を取ります。選択肢が多いときや場所が足りないときは、選んだものだけを表示するリストやメニューを使います。
安全で、選ばれる見込みの高い選択肢があれば、最初からそれを選んだ状態にしておいてかまいません。利用者に意識して答えてほしいときは、どれも選ばない状態で始めます。一度どれかを選ぶと、グループを「何も選んでいない」状態には戻せません。ラジオボタンは自分自身の操作では選択が外れないからです。「なし」も正しい答えになりうるなら、選択肢の 1 つとして加えます。
| コントロール | 選択 | 反映されるとき | 用途 |
|---|---|---|---|
| チェックボックス | 独立した選択をいくつでも(0 個も可) | 利用者が確定したとき | フォームの選択肢、規約への同意、行の選択 |
| ラジオボタン | すべて見えている少数の選択肢から 1 つだけ | 多くは、利用者が確定したとき | 1 つの問いへの 1 つの答え |
| スイッチ | 1 つの設定のオンかオフ | すぐに | 切り替えた時点で反映される設定 |
- どのグループにも名前を付けます。選択肢が答える問いを、グループの前に見えるテキストとして置きます。
- 選択肢は縦に並べ、小さい順など、問いに合った順序にします。各選択肢がターゲットの高さをすでに確保しているので、並べるときに隙間を足す必要はありません。
- ラベルは短く、形をそろえて、見比べやすくします。
- 1 つのグループの選択肢は、1 つのコンテナにまとめます。
- ラジオボタンを 1 つだけで使わない。「はい」か「いいえ」かの 1 つの選択には、チェックボックスかスイッチを使います。
- 互いに排他的でない選択にラジオボタンを使わない。チェックボックスを使います。
- 選択肢を選んだ瞬間に、別の画面への移動のような大きなアクションを始めない。矢印キーは通過する選択肢を順に選んでいくので、キーボードの利用者は目的の選択肢にたどり着く前にそのアクションを起こしてしまいます。
- 1 つのグループを、画面の離れた場所に分けて置かない。
- ロール
radio- ラベル付け
- label is each option's accessible name, visible or not (labelHidden). The group's name (the question the options answer) is the radio group's label. Where options are grouped without a radio group (RAD-25) the product gives it: on the web a container with role radiogroup and aria-labelledby; on the other platforms the group container's label.
- 最小ターゲット
- 見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px
キーボード操作
| キー | 動作 |
|---|---|
| Tab / Shift+Tab | Moves focus into the group (to the selected option, or the first enabled one when none is selected) and out of it. |
| Space | Selects the focused option if it is not selected; nothing while it is read-only. |
| Down Arrow / Right Arrow | Moves to the next enabled option and selects it, wrapping (Left Arrow in right-to-left); an option that is read-only is focused and not selected. |
| Up Arrow / Left Arrow | Moves to the previous enabled option and selects it, wrapping (Right Arrow in right-to-left); an option that is read-only is focused and not selected. |
| Enter | Nothing on the web (it submits the form); selects where the platform activates any focused control with Enter (Android, tvOS). |
備考
- Scope: this spec covers the option, and what holds among the options of any group however it is formed (RAD-3, RAD-4, RAD-5, RAD-16). The radio group component (`radio-group`) is the group in the usual case: it holds the value, names the set and draws its label, its description and its error. Grouping without it (RAD-25) is for options that one container cannot lay out — one in each row of a table or a list, one on each card — and draws nothing, so it has no tokens.
- iOS has no radio role: an option is exposed as a button with the selected trait, and its position ("2 of 3") through its accessibility value.
- The ring alone carries the selected condition at 3:1 against the surface (WCAG 1.4.11): the selected ring is sys.color.primary, which the build holds to 4.5:1 on every surface role, and its stroke is 2.5 times the unselected one, so the selection does not rest on color alone (WCAG 1.4.1). The translucent fill is not counted for that, but it is a part of the mark, and WCAG 1.4.11 measures a part against the colors next to it: where the fill meets the ring (fill.inset 0) it is held 3:1 against the ring, and where an inset leaves a gap (a dot) it is held 3:1 against the gap, which shows what the radio stands on. The build holds it as drawn — the fill at comp.radio-button.fill.opacity and the ring, each over the state layer of the state — at rest, hovered, focused and pressed (a disabled option is exempt, as WCAG exempts an inactive control), on every surface role, in every scheme; in the default system the fill is the ring's color at 16 %, 3.17:1 from the ring at the least (the focused error form on surface-container-highest in light). In an error form the ring is sys.color.error, which the build holds to 4.5:1 on every surface role as it holds the primary role, so both error forms keep 3:1 (WCAG 1.4.11) and the selection is still told by the stroke.
- A group in error is said by the radio group — its error text and its invalid state (RGP-8) — and shown on every option of it: the ring, and the fill of the selected one, take the error color (RAD-26), as a checkbox's box does (CHK-4). The color is one more sign and never the only one (WCAG 1.4.1): the words and the state carry the error, and the option exposes no invalid state of its own, because the radio role has none. The option has an error condition so that one form marks its checkboxes and its radio buttons by one rule.
- Read-only (RAD-28) is said by the group, never by the option: WAI-ARIA has aria-readonly on radiogroup and not on radio. A radio group that is read-only says it (RGP-20); options grouped by name alone are read-only as a set when each is, and the product says it on the container it gives them (on the web, aria-readonly on the element with role radiogroup). HTML's readonly attribute does not apply to a radio button, so the option cancels its activation itself.
生成元 specs/radio-button.spec.json, tokens/sys/measure.tokens.json
- 各選択肢は、ラジオのロール、選ばれているかどうか、無効状態、グループ内での位置とグループの大きさ(「2 of 3」など)を伝えます。Apple のプラットフォームにはラジオのロールがないため、選択トレイトを持つボタンとして伝え、位置はアクセシビリティの値で伝えます。Flutter のセマンティクスでは、位置はまだ伝えられません。
- ラベルは、描かれていても
labelHiddenで隠されていても、アクセシブルネームになります。描かれたテキストは支援技術から隠すので、名前が読まれるのは 1 度だけです。 - グループの中で Tab で止まる位置は 1 つです。選ばれている選択肢、何も選ばれていなければ最初の有効な選択肢です。矢印キーは次または前の有効な選択肢に移ってそれを選び、端では反対側に戻ります。右から左のレイアウトでは、左右の矢印キーの向きが入れ替わります。
- Space でフォーカスのある選択肢を選びます。Enter で選べるのは、フォーカスのあるコントロールを Enter で操作するのが慣習のプラットフォーム(Android、tvOS)だけです。Web では Enter はフォームを送信します。
readOnlyの間、または周りのラジオグループが読み取り専用の間は、選択肢はフォーカスでき、矢印キーでもたどれますが、押下、Space、ラベルのクリック、click()、矢印キーでの到着のどれでも選ばれません。ホバーと押下の反応も出ません。radio ロールには読み取り専用の状態がないので、それを伝えるのはグループです。- 選択肢を選ぶと、ほかの選択肢の選択は外れます。変更を知らせるのは新しく選ばれた選択肢だけで、すでに選ばれている選択肢を操作しても何も起きません。
- ラベルをクリックまたはタップすると、その選択肢が選ばれます。ラベルもヒット領域に含まれ、ヒット領域は最小ターゲットを満たします。各選択肢はターゲットの高さをレイアウトで確保するので、縦に並べた選択肢もそれぞれ独立したターゲット全体を持ちます。
- 選択を伝えるのはリングだけです。既定のシステムでは、選ばれたリングはどのサーフェスに対しても 3:1 以上を保ち、選ばれていないリングの 2.5 倍の太さがあります。半透明の塗りはそこに数えませんが、接するリングとの間で、どの状態、どのサーフェス、どのスキームでも 3:1 以上を保ちます(WCAG 1.4.11)。選んだ選択肢の中心も見分けられます。
- フォーカスインジケーターはキーボードフォーカスのときだけ表示します。
- 動きを減らす設定では、新しい状態をすぐに、完全に示します。
- ラベルがない場合は、デバッグビルドで報告します。
プラットフォームごとの既知の制約:
- SwiftUI、Compose、Flutter、React Native は、まだ以前の寸法で点のある以前のラジオボタンを描き、リングと塗りを 1 つの形として描くことにも対応しておらず、ターゲットの高さもレイアウトで確保しません。そのため、隙間なく並べた選択肢はターゲットを分け合います。
readOnlyはいまのところ Web だけにあります。SwiftUI、Compose、Flutter、React Native にはまだありません。m3-likeでは、選ばれたときの点は元の大きさのままフェードインします。Material では中心から拡大して現れます(mimicry-gaps 台帳radio-dot-scale-in)。
- グループに、答える問いを名前として付けます。Web では選択肢を
role="radiogroup"とaria-labelledbyを持つ要素、または<legend>を持つ<fieldset>で囲みます。Flutter ではTanreiRadioGroupのsemanticLabel、React Native ではRadioGroupのaccessibilityLabelを設定します。 - キー操作と位置の読み上げが働くように、グループは各プラットフォームの方法で作ります。Web では同じ
name、Compose では親のModifier.selectableGroup()、Flutter ではTanreiRadioGroup、React Native ではRadioGroupです。 - 選択肢を自分で
nameによってまとめ、どれも読み取り専用にするときは、それをコンテナで伝えます。role="radiogroup"の隣にaria-readonly="true"を付けてください。readOnlyを付けたラジオグループなら、これを代わりに行います。 - 必須の問いに答えがないときは、グループにメッセージを表示します。エラーのラジオグループは、エラーテキストを描き、すべてのラジオボタンをエラーの形にします。ラジオグループを使わずにまとめた選択肢では、それぞれのラジオボタンに
errorを設定します。チェックボックスのボックスと同じように、リングと中の丸がエラーの色になります。無効の選択肢は、エラーの色になりません。 - エラーのラジオボタンが invalid として伝えられることはありません。ラジオボタンのロールには、その状態がないためです。伝えるのはグループです。ラジオグループは invalid になり、エラーテキストがその説明になります。選択肢を自分でまとめる場合は、そのそばにエラーを言葉で書きます。
- 自分のレイヤでリングと塗りを変えるときは、リングだけで選択が分かる強さを保ちます。ビルドでは確かめません。
- React Native では、英語以外のプロダクトなら
RadioGroupにpositionTextを渡します。既定では、位置を英語で「2 of 3」のように読み上げます。