Skip to content
fandhe-frontendcore v0.4.3
GitHub

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"> に無効な属性のため反映しません)。

キーボード操作

キー対象効果
TabRootグループへフォーカスを移します(選択済み項目があればその項目、無ければ先頭項目)。ネイティブ radio group のロービングタブインデックスに従います。
SpaceItemフォーカス中の未選択項目を選択します。
ArrowDown / ArrowRightItem次の項目へ移動し選択します(循環)。
ArrowUp / ArrowLeftItem前の項目へ移動し選択します(循環)。
Home / EndItem先頭/末尾の項目へ移動し選択します。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 のレイアウトの最小例です。

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;
}

Demo

枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。

Plan
Plan (horizontal)
Plan (item disabled)
Plan (group disabled)
Plan (invalid + required)
Plan (read-only)

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-input

API Reference

Arguments

NameTypeDefaultDescription
root/label(props).disabledboolfalsedata-disabled(root/label)と aria-disabled="true"(root、true 時のみ)を反映する。
root/label(props).invalidboolfalsedata-invalid(root/label)を反映する。
root/label(props).requiredboolfalsedata-required(root/label)と aria-required="true"(root、true 時のみ)を反映する。
root(props).readonlyboolfalsearia-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/invalidboolfalsedata-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/requiredboolfalseネイティブ disabled/required 属性(true 時のみ)を反映する。readonly はネイティブ <input type="radio"> に無効な属性のため反映しない。
item_hidden_input(props).invalidboolfalsearia-invalid="true"(true 時のみ)を反映する。
item_hidden_input(name)Option<&str>—Some のとき name 属性を出力する(グループ内排他選択の同一 name グループ)。
item_hidden_input(value)&str—ネイティブ value 属性。

Data Attributes

PartAttributeObserved Values
itemdata-disabled(値なし)
itemdata-invalid(値なし)
itemdata-readonly(値なし)
itemdata-statechecked, unchecked
itemdata-valuemonthly, yearly
item-controldata-disabled(値なし)
item-controldata-invalid(値なし)
item-controldata-readonly(値なし)
item-controldata-statechecked, unchecked
item-hidden-inputdata-statechecked, unchecked
item-textdata-disabled(値なし)
item-textdata-invalid(値なし)
item-textdata-readonly(値なし)
item-textdata-statechecked, unchecked
labeldata-disabled(値なし)
labeldata-invalid(値なし)
labeldata-required(値なし)
rootdata-disabled(値なし)
rootdata-invalid(値なし)
rootdata-orientationhorizontal
rootdata-required(値なし)

Examples

Vertical, with disabled item

orientation: Some(Orientation::Vertical) + 2 番目の項目を disabled にした例。

Delivery

Custom CSS

自前 CSS を data-scope/data-part/data-* セレクタで当てる最小例。

headless-ui はスタイルレスです。data-scope/data-part/data-* をセレクタに使い、以下のような CSS を自前で当てられます。

Plan
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

KeyDescription
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

AttributeDescription
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-checkeditem_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/矢印キー)が成立する。