Skip to content
fandhe-frontendcore v0.4.3
GitHub

Tabs

WAI-ARIA APG の Tabs パターンに準拠したタブ切り替え UI です。fandhe-frontend-headless-ui の tabs mod が構造・アクセシビリティ(WAI-ARIA・キーボード操作)・表示状態(data-*)のみを提供する unstyled 部品であり、Themes 版と異なりスタイル(CSS)は一切持ちません。ActivationMode(Automatic/Manual)や Orientation(Horizontal/Vertical)を切り替えられます。

スタイル済みの表示例は Tabs を参照してください。

キーボード操作

キー対象効果
Tab / Shift+TabRootroving tabindex により active(無ければ最初の非 disabled)trigger へフォーカス移動します。trigger から Tab で active content(tabindex="0"、inactive は hidden)へ移動します(配線なし、ブラウザ既定)。
ArrowRight / ArrowLeft(horizontal)・ArrowDown / ArrowUp(vertical)Trigger次/前の非 disabled trigger へフォーカスを移動します(fandhe-frontend-wasm-full の keynav.rs::tabs_next_index)。data-loop-focus="false" のときのみ端で停止し、既定(true)では反対端へ循環します。ActivationMode::Automatic では同時に活性化します。
Home / EndTrigger最初/最後の非 disabled trigger へフォーカスを移動します。Automatic では同時に活性化します。
Enter / SpaceTriggerネイティブ button の click 委譲(handle_trigger_click)で活性化します。ActivationMode::Manual での主な活性化手段で、Automatic でもクリックと同じ経路で動作します。disabled trigger は no-op です。

参考サイトとの差分

イシュー #1656 で ark-ui(Zag.js)/ Radix Primitives / Radix Themes / chakra-ui と突合しました。本実装は Radix Primitives の data-* 表(root: data-orientation / list: data-orientation / trigger: data-state="active"|"inactive", data-disabled / content: data-state, data-orientation)と属性単位で一致し、ark-ui の 5 パーツ(root/list/trigger/content/indicator)とも一致するため、是正は行っていません(パート・data-* の増減なし)。

  • ark-ui の data-selected(trigger/content): 本実装は Radix 語彙 data-state="active"|"inactive" を #528 で採用済みで、同義のため非採用です。
  • ark-ui の data-focus(root/list/trigger)・data-ssr(trigger): 実行時フォーカス状態・ハイドレーション判定マーカーであり、SSR 静的出力の責務外です。:focus-visible で代替できます。
  • ark-ui の lazyMount/unmountOnExit/hideMode/composite/navigate/onValueChange/onFocusChange/制御 value、Radix Content の forceMount: 実行時マウント制御・イベント関心のため非採用です。本実装は全 content を常時マウントし inactive を hidden で隠します(forceMount 相当の固定挙動)。
  • ark-ui の deselectable: SSR では TabsProps::selected がどの value にも一致しない場合に全 inactive で描画できるため、静的表現としては充足しています。実行時の解除トグルは wasm 層の将来課題です。
  • Radix Root の dir: RTL は本リポジトリ横断で未採用です。
  • chakra-ui の Tabs.ContentGroup(アニメーション用ラッパー)と variant/size/fitted/justify/colorPalette: 装飾・アニメーション関心は headless 層へ持ち込まず、Themes 層(pre-styled-ui::tabs)の責務です。
  • Radix Themes の TabNav: 別部品(Themes 層 tab_nav)が担うため対象外です。

一方で、本実装は list の data-activation-mode/data-loop-focus(fandhe-frontend-wasm-full との契約)・indicator の data-state/aria-hidden="true"/hidden(active なし時の fail-safe)を参照サイトに無い superset として持ちますが、これらは削除していません。

関連 API: fandhe-frontend-headless-ui API

自前 CSS の最小例

Themes 版を使わず本部品を直接使う場合、[data-scope="tabs"][data-part="..."] セレクタと data-state/data-disabled/data-orientation/[hidden] で見た目を組み立てます。

CSS
[data-scope="tabs"][data-part="list"] {
  display: flex;
  gap: 0.5rem;
}

[data-scope="tabs"][data-part="root"][data-orientation="vertical"] [data-part="list"] {
  flex-direction: column;
}

[data-scope="tabs"][data-part="trigger"][data-state="active"] {
  border-bottom: 2px solid #2563eb;
  font-weight: 600;
}

[data-scope="tabs"][data-part="trigger"][data-disabled] {
  opacity: 0.5;
}

[data-scope="tabs"][data-part="trigger"]:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

[data-scope="tabs"][data-part="content"][hidden] {
  display: none;
}

[data-scope="tabs"][data-part="indicator"] {
  position: absolute;
  left: var(--left);
  width: var(--width);
}

Demo

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

Overview panel content.

Features

  • WAI-ARIA APG の Tabs パターン(role="tablist"/"tab"/"tabpanel"、aria-selected、相互参照する aria-controls/aria-labelledby、roving tabindex)に準拠したマークアップを組み立てる(tabs.rs モジュール doc)。
  • root / list / trigger / content の 4 パーツに加え、選択タブの位置を示す装飾パーツ indicator(opt-in、TabsProps::indicator)を持つ 5 パーツ構成。
  • ActivationMode(Automatic/Manual)を data-activation-mode として出力し、wasm-full 側のキーボード操作時の活性化挙動分岐に使われる(Automatic が既定)。
  • props.selected がどの value とも一致しない場合、または一致した項目が disabled の場合は全 trigger/content が inactive として決定的に描画される(panic しない)。
  • 参照突合(イシュー #1656): ark-ui(Zag.js)/ Radix Primitives / Radix Themes / chakra-ui と突合し是正なし。data-state="active"|"inactive" は Radix 語彙で、ark の data-selected は同義のため非採用。data-focus/data-ssr(実行時フォーカス・ハイドレーション判定)・lazyMount/unmountOnExit/deselectable/dir/Tabs.ContentGroup はいずれも SSR 静的出力の責務外・装飾関心として非採用(intentional-non-adoption.md §3.25 規則 2 等)。

Anatomy

root
  list
    trigger
    indicator
  content

API Reference

Arguments

NameTypeDefaultDescription
props(id)&str—trigger/content の決定的 id 生成("{id}-trigger-{value}"/"{id}-content-{value}")に使うベース id。
props(selected)&str—選択中タブの value(SSR 時点の静的選択状態)。どの TabItem::value とも一致しない場合は全タブ非選択として描画する。
props(orientation)Orientation—data-orientation(root/list/trigger/content 共通)・list の aria-orientation の双方に反映する。
props(activation_mode)ActivationModeAutomaticlist へ data-activation-mode として出力する(フォーカス移動と同時に活性化 or Enter/Space で活性化)。
props(loop_focus)bool—list へ data-loop-focus として出力する(Arrow キーで端から反対端へ循環するか。ark-ui 既定 true 相当、呼び出し側が明示指定する)。
props(indicator)boolfalsetrue のとき list の最終子として indicator(選択タブの位置を示す装飾パーツ)を追加する。
TabItem(value)&str—タブの識別 value。trigger の data-value・id/aria-controls/aria-labelledby の決定的生成に使う。
TabItem(disabled)bool—true のとき disabled 属性・data-disabled・aria-disabled="true" を trigger に付与し、roving tabindex のフォールバック候補からも除外する。

Data Attributes

PartAttributeObserved Values
contentdata-orientationhorizontal
contentdata-stateactive, inactive
indicatordata-orientationhorizontal
indicatordata-stateactive
listdata-activation-modeautomatic
listdata-loop-focustrue
listdata-orientationhorizontal
rootdata-orientationhorizontal
triggerdata-disabled(値なし)
triggerdata-orientationhorizontal
triggerdata-stateactive, inactive
triggerdata-valuebilling, overview, settings

Examples

Vertical orientation・Manual activation・indicator なし

Demo とは異なる軸(縦向き・手動活性化・indicator 省略)の組み合わせを実演します。

Profile panel content.

自前 CSS の最小例

data-scope / data-part / data-state / data-disabled 属性セレクタで見た目を組み立てる最小例です。headless-ui 自体はスタイルを持ちません。

Overview panel content.
CSS
[data-scope="tabs"][data-part="list"] {
  display: flex;
  gap: 0.5rem;
}
[data-scope="tabs"][data-part="root"][data-orientation="vertical"] [data-part="list"] {
  flex-direction: column;
}
[data-scope="tabs"][data-part="trigger"][data-state="active"] {
  border-bottom: 2px solid #2563eb;
  font-weight: 600;
}
[data-scope="tabs"][data-part="trigger"][data-disabled] {
  opacity: 0.5;
}
[data-scope="tabs"][data-part="trigger"]:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
[data-scope="tabs"][data-part="content"][hidden] {
  display: none;
}
[data-scope="tabs"][data-part="indicator"] {
  position: absolute;
  left: var(--left);
  width: var(--width);
}

Accessibility

Keyboard Interactions

KeyDescription
Tab / Shift+Tabroving tabindex により active(無ければ最初の非 disabled)trigger にフォーカスする。trigger から Tab で active content(tabindex="0"、inactive は hidden)へ移動する(配線なし、ブラウザ既定)。
ArrowRight / ArrowLeft(horizontal)・ArrowDown / ArrowUp(vertical)次/前の非 disabled trigger へフォーカスを移動する(fandhe-frontend-wasm-full の keynav.rs::tabs_next_index)。data-loop-focus="false" のときのみ端で停止し、既定(true)では反対端へ循環する。ActivationMode::Automatic では同時に活性化(handle_tabs_keydown が activate_tab を呼ぶ)。
Home / End最初/最後の非 disabled trigger へフォーカスを移動する。Automatic では同時に活性化する(keynav.rs::tabs_next_index)。
Enter / Spaceネイティブ button の click(wasm-full の click 委譲、handle_trigger_click)で活性化する。ActivationMode::Manual での主な活性化手段であり、Automatic でもクリックと同じ経路で動作する。disabled trigger は no-op。

WAI-ARIA

AttributeDescription
role="tablist" / aria-orientationlist に付与。
role="tab" / aria-selected / aria-controlstrigger に付与。選択状態と対応する content の id を表す。
role="tabpanel" / aria-labelledbycontent に付与。対応する trigger の id を指す。