Radio Group
fandhe-frontend-headless-ui の radio_group mod が提供する「高々 1 項目が選択される」ラジオグループの unstyled 部品です。Root / Label / Item / ItemControl / ItemText / ItemHiddenInput の 6 anatomy パーツを持ち、意味論・キーボード操作・排他選択はネイティブ <input type="radio">(ItemHiddenInput)に委ねる構造です。
/themes/ 側の RadioGroup(Chakra ライクな variant/size を持つスタイル層)と異なり、CSS を一切持ちません。
RadioGroupProps(disabled/readonly/invalid/required)は次のように各パーツへ反映されます。root/label は data-disabled/data-invalid/data-required を付与し、root はさらに disabled/readonly/required が true のときのみ対応する aria-disabled/aria-readonly/aria-required="true"(radiogroup ロールの Supported States)を付与します。item/item-control/item-text は data-disabled/data-readonly/data-invalid を付与します(data-required は item 系パーツに無い属性です)。item-control は意味論を持たない装飾パーツであることを明示するため aria-hidden="true" を常時付与します。item-hidden-input には invalid: true のときのみ aria-invalid="true" を、required: true のときのみネイティブ required 存在属性を付与します(readonly はネイティブ <input type="radio"> に無効な属性のため反映しません)。
キーボード操作
| キー | 対象 | 効果 |
|---|---|---|
| Tab | Root | グループへフォーカスを移します(選択済み項目があればその項目、無ければ先頭項目)。ネイティブ radio group のロービングタブインデックスに従います。 |
| Space | Item | フォーカス中の未選択項目を選択します。 |
| ArrowDown / ArrowRight | Item | 次の項目へ移動し選択します(循環)。 |
| ArrowUp / ArrowLeft | Item | 前の項目へ移動し選択します(循環)。 |
| Home / End | Item | 先頭/末尾の項目へ移動し選択します。ark-ui / Radix Primitives の Keyboard 表には無い拡張ですが、WAI-ARIA APG のオプション挙動として提供します。 |
実 DOM 配線は fandhe-frontend-wasm-full の keynav(radio_next_index、orientation で軸制限・循環)が矢印キー/Home/End を担いますが、JS なしでも同一 name のネイティブ radio としてブラウザ標準操作(Tab/Space/矢印キー)が成立します。
参考サイトとの差分
ark-ui 公式 Data Attributes / Keyboard Support 表(zag radio-group.connect.ts)・Radix Primitives radio-group と突合し、上記のキーボード操作・data-*/aria-* 語彙・anatomy を是正しました。一方、以下は意図的に合わせていません。
- ark の
Indicator(選択項目へ追従する浮動ビジュアル、位置style計算)はレイアウト計測の関心のため headless へ持ち込みません(docs/policy/intentional-non-adoption.md§3.25 規則 2)。 data-orientationはrootのみへ付与し、子パーツへ伝播しません(checkbox_groupと同判断。CSS は子孫セレクタで足ります)。data-hover/data-active/data-focus(ark)は SSR 静的出力に持たせません(checkboxと同じ契約。キーボード操作専用のdata-focus-visibleはfandhe-frontend-wasm-fullが合成します)。item-hidden-inputはdata-stateを維持します(ark は持ちませんが、fandhe-frontend-wasm-fullのkeynavが同期する既存契約のためです)。readonlyはrootへdata-readonlyを出力しません(ark の Root Data Attributes 表にdata-readonlyは無いためです。表示契約としてのdata-readonlyはitem/item-control/item-textのみへ出力します)。
スタイル済みの表示例は Radio Group を参照してください。
関連 API: fandhe-frontend-headless-ui API
自前 CSS の最小例
Themes 版を使わず本部品を直接使う場合、[data-scope="radio-group"][data-part="..."] セレクタでスタイルを当てます。以下は item-control の円形描画、checked/disabled/invalid の見た目、フォーカスリング、horizontal orientation のレイアウトの最小例です。
[data-scope="radio-group"][data-part="item-control"] {
display: inline-block;
width: 1rem;
height: 1rem;
border-radius: 999px;
border: 1px solid #ccc;
}
[data-scope="radio-group"][data-part="item-control"][data-state="checked"] {
box-shadow: inset 0 0 0 3px #06c;
}
[data-scope="radio-group"][data-part="item-control"][data-disabled] {
opacity: 0.5;
}
[data-scope="radio-group"][data-part="item-control"][data-invalid] {
border-color: #d33;
}
[data-scope="radio-group"][data-part="item"]:focus-within {
outline: 2px solid #06c;
outline-offset: 2px;
}
[data-scope="radio-group"][data-part="root"][data-orientation="horizontal"] {
display: flex;
gap: 1rem;
}Demo
枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。
Features
- Root / Label / Item / ItemControl / ItemText / ItemHiddenInput の 6 anatomy パーツと、「高々 1 項目が選択される」状態機械(
crate::state::SingleSelect埋め込み)を提供する。 item_hidden_inputが生成するネイティブ<input type="radio">がチェック状態・フォーム送信・グループ内排他選択を担うため、item_controlにはrole="radio"/aria-checkedを重複付与せず、代わりにaria-hidden="true"を常時付与する(二重読み上げ防止、イシュー #1616)。itemは<label>要素であり、内包するitem_hidden_inputへのクリック委譲が JS なしで機能する。data-state値語彙("checked"/"unchecked")は Checkbox / Switch と共有する共通機械由来であり、本モジュール独自の値を作らない。RadioGroupProps(disabled/readonly/invalid/required)が各パーツのdata-*/ARIA 属性を決定する。パート別の反映属性は ark-ui の Data Attributes 表に従いパートごとに異なる(下記 arguments 参照、イシュー #1616)。
Anatomy
root
label
item
item-control
item-text
item-hidden-inputAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
root/label(props).disabled | bool | false | data-disabled(root/label)と aria-disabled="true"(root、true 時のみ)を反映する。 |
root/label(props).invalid | bool | false | data-invalid(root/label)を反映する。 |
root/label(props).required | bool | false | data-required(root/label)と aria-required="true"(root、true 時のみ)を反映する。 |
root(props).readonly | bool | false | aria-readonly="true"(root、true 時のみ)のみを反映する。ark-ui の Root Data Attributes 表に data-readonly は無い。 |
root(orientation) | Option<Orientation> | — | Some のときのみ data-orientation/aria-orientation を付与する。 |
root(labelled_by) | Option<&str> | — | Some のときのみ aria-labelledby を付与する(label(id) と対で使う)。 |
label(id) | Option<&str> | — | root(labelled_by) の参照先 id。 |
item/item_control/item_text(props).disabled/readonly/invalid | bool | false | data-disabled/data-readonly/data-invalid を反映する(data-required は item 系パーツに無い)。 |
item/item_control/item_text/item_hidden_input(checked) | bool | — | data-state の checked/unchecked を決める。 |
item(value) | &str | — | 選択肢の値。data-value として動的値のまま出力する(クリック → dispatch payload の源)。 |
item_hidden_input(props).disabled/required | bool | false | ネイティブ disabled/required 属性(true 時のみ)を反映する。readonly はネイティブ <input type="radio"> に無効な属性のため反映しない。 |
item_hidden_input(props).invalid | bool | false | aria-invalid="true"(true 時のみ)を反映する。 |
item_hidden_input(name) | Option<&str> | — | Some のとき name 属性を出力する(グループ内排他選択の同一 name グループ)。 |
item_hidden_input(value) | &str | — | ネイティブ value 属性。 |
Data Attributes
| Part | Attribute | Observed Values |
|---|---|---|
item | data-disabled | (値なし) |
item | data-invalid | (値なし) |
item | data-readonly | (値なし) |
item | data-state | checked, unchecked |
item | data-value | monthly, yearly |
item-control | data-disabled | (値なし) |
item-control | data-invalid | (値なし) |
item-control | data-readonly | (値なし) |
item-control | data-state | checked, unchecked |
item-hidden-input | data-state | checked, unchecked |
item-text | data-disabled | (値なし) |
item-text | data-invalid | (値なし) |
item-text | data-readonly | (値なし) |
item-text | data-state | checked, unchecked |
label | data-disabled | (値なし) |
label | data-invalid | (値なし) |
label | data-required | (値なし) |
root | data-disabled | (値なし) |
root | data-invalid | (値なし) |
root | data-orientation | horizontal |
root | data-required | (値なし) |
Examples
Vertical, with disabled item
orientation: Some(Orientation::Vertical) + 2 番目の項目を disabled にした例。
Custom CSS
自前 CSS を data-scope/data-part/data-* セレクタで当てる最小例。
headless-ui はスタイルレスです。data-scope/data-part/data-* をセレクタに使い、以下のような CSS を自前で当てられます。
[data-scope="radio-group"][data-part="item-control"] {
display: inline-block;
width: 1rem;
height: 1rem;
border-radius: 999px;
border: 1px solid #ccc;
}
[data-scope="radio-group"][data-part="item-control"][data-state="checked"] {
box-shadow: inset 0 0 0 3px #06c;
}
[data-scope="radio-group"][data-part="item-control"][data-disabled] {
opacity: 0.5;
}
[data-scope="radio-group"][data-part="item-control"][data-invalid] {
border-color: #d33;
}
[data-scope="radio-group"][data-part="item"]:focus-within {
outline: 2px solid #06c;
outline-offset: 2px;
}
[data-scope="radio-group"][data-part="root"][data-orientation="horizontal"] {
display: flex;
gap: 1rem;
}Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Tab | グループへフォーカスを移す(選択済み項目があればその項目、無ければ先頭項目)。ネイティブ radio group のロービングタブインデックスに従う。 |
| Space | フォーカス中の未選択項目を選択する。 |
| ArrowDown / ArrowRight | 次の項目へ移動し選択する(循環)。 |
| ArrowUp / ArrowLeft | 前の項目へ移動し選択する(循環)。 |
| Home / End | 先頭/末尾の項目へ移動し選択する。ark-ui / Radix Primitives の Keyboard 表には無いが、WAI-ARIA APG のオプション挙動として fandhe-frontend-wasm-full の keynav(radio_next_index)が拡張として実装する。 |
WAI-ARIA
| Attribute | Description |
|---|---|
| role="radiogroup" (root) | 固定付与。 |
| aria-required / aria-readonly / aria-disabled (root) | 対応する RadioGroupProps フィールドが true のときのみ付与する(radiogroup ロールの Supported States、イシュー #1616)。 |
| aria-hidden="true" (item-control) | 意味論を持たない装飾パーツであることを明示するため常時付与する(イシュー #1616)。 |
| role="radio" / aria-checked | item_control へは明示付与しない。意味論は item_hidden_input のネイティブ <input type="radio"> が担う(二重読み上げ防止)。 |
| aria-invalid (item-hidden-input) | props.invalid が true のときのみ "true" を出力する(valid のときは属性自体を省略する、イシュー #1616)。 |
| required (item-hidden-input) | props.required が true のときのみネイティブ存在属性として出力する(イシュー #1616)。 |
| 実 DOM 配線 | fandhe-frontend-wasm-full の keynav(radio_next_index、orientation で軸制限・循環)が矢印キー/Home/End を配線するが、JS なしでも同一 name のネイティブ radio としてブラウザ標準操作(Tab/Space/矢印キー)が成立する。 |