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+Tab | Root | roving 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 / End | Trigger | 最初/最後の非 disabled trigger へフォーカスを移動します。Automatic では同時に活性化します。 |
| Enter / Space | Trigger | ネイティブ 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] で見た目を組み立てます。
[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 自体はスタイルを持ちません。
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
contentAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
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) | ActivationMode | Automatic | list へ data-activation-mode として出力する(フォーカス移動と同時に活性化 or Enter/Space で活性化)。 |
props(loop_focus) | bool | — | list へ data-loop-focus として出力する(Arrow キーで端から反対端へ循環するか。ark-ui 既定 true 相当、呼び出し側が明示指定する)。 |
props(indicator) | bool | false | true のとき 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
| Part | Attribute | Observed Values |
|---|---|---|
content | data-orientation | horizontal |
content | data-state | active, inactive |
indicator | data-orientation | horizontal |
indicator | data-state | active |
list | data-activation-mode | automatic |
list | data-loop-focus | true |
list | data-orientation | horizontal |
root | data-orientation | horizontal |
trigger | data-disabled | (値なし) |
trigger | data-orientation | horizontal |
trigger | data-state | active, inactive |
trigger | data-value | billing, overview, settings |
Examples
Vertical orientation・Manual activation・indicator なし
Demo とは異なる軸(縦向き・手動活性化・indicator 省略)の組み合わせを実演します。
自前 CSS の最小例
data-scope / data-part / data-state / data-disabled 属性セレクタで見た目を組み立てる最小例です。headless-ui 自体はスタイルを持ちません。
[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
| Key | Description |
|---|---|
| Tab / Shift+Tab | roving 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
| Attribute | Description |
|---|---|
| role="tablist" / aria-orientation | list に付与。 |
| role="tab" / aria-selected / aria-controls | trigger に付与。選択状態と対応する content の id を表す。 |
| role="tabpanel" / aria-labelledby | content に付与。対応する trigger の id を指す。 |