Action Bar
複数選択(チェックボックス等)に対する一括操作を提示する画面下部固定の操作バーです。fandhe-frontend-headless-ui の action_bar mod が構造・アクセシビリティ(WAI-ARIA・キーボード操作)・表示状態(data-*)のみを提供する unstyled 部品であり、Themes 版と異なりスタイル(CSS)は一切持ちません。開閉状態は Disclosure を埋め込んだ状態機械が管理しますが、「選択操作から開閉状態を決定する」判断自体は呼び出し側アプリケーションの責務です。content は role="dialog"(非モーダル)で表され、開状態のときのみ data-expanded が付与されます。
スタイル済みの表示例は Action Bar を参照してください。
キーボード操作
| キー | 対象 | 効果 |
|---|---|---|
| Escape | Content | バーを閉じます(fandhe-frontend-wasm-full の OverlayKind::ActionBar が配線。data-close-on-escape="false" で opt-out できます)。 |
| Tab / Shift+Tab | Root 配下のボタン | ネイティブ button 要素間のフォーカス移動です。開いた瞬間にフォーカスが自動で移動することはありません(content は tabindex="-1" を固定で持ち、chakra-ui の autoFocus: false と同じ挙動です)。 |
参考サイトとの差分(イシュー #1647)
ark-ui・Radix Primitives のいずれにも ActionBar 相当のコンポーネントは存在しません。参照できるのは chakra-ui の ActionBar のみで、その実体は独自の状態機械を持たず Ark Popover(zag.js popover.connect)をそのまま再利用しています。このため本部品の属性仕様は zag.js popover の content/close-trigger の出力を基準にしています。
- 是正した点:
contentのroleを"toolbar"(矢印キーでの roving tabindex を伴わない不完全な適用でした)から"dialog"(非モーダル、aria-modalは付与しません)へ変更しました。あわせて開状態のときのみdata-expandedを付与し、tabindex="-1"を固定で付与するようにしました(呼び出し側がtabindexを指定していれば出力しません)。close-triggerは呼び出し側がaria-labelを指定しておらず、かつ children が空(可視テキストを持たないアイコンのみボタン)のときに限り既定値"close"(zag.js popover のtranslations.closeTrigger既定値と同じ)を出力します。children に可視テキストがある場合は既定aria-labelを出力しません(WCAG 2.5.3 Label in Name 対策)。 - 意図的に合わせていない点:
root(hydration のルート要素として DOM を持つ必要があるため、DOM を描画しない参考サイトのRootとは異なります)・positioner(hidden存在属性を pre-styled-ui が利用するため付与しており、参考サイトは素のdivです)・separator(role="separator"+aria-orientation="vertical"をアクセシビリティ上の superset として維持しており、参考サイトは素のdivです)。data-placement/data-side(配置バリエーション)はスタイル層(fandhe-frontend-pre-styled-ui)の責務としてスコープ外のままです(docs/policy/intentional-non-adoption.md§3.25 規則 2)。外側クリックでの閉鎖(closeOnInteractOutside)も既定で無効のまま opt-in 属性を持ちません — 選択操作用のチェックボックス等は ActionBar の外側(一覧側)に存在するため、外側クリックでの誤閉鎖を防ぐ安全側の判断です。
関連 API: fandhe-frontend-headless-ui API
自前 CSS の最小例
Themes 版を使わず本部品を直接使う場合、data-scope/data-part 属性セレクタと data-state/data-expanded の有無で見た目を組み立てます。
[data-scope="action-bar"][data-part="positioner"] {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
}
[data-scope="action-bar"][data-part="content"][data-state="open"] {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
[data-scope="action-bar"][data-part="close-trigger"]:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}Demo
枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。
Features
- 複数選択操作バー(chakra-ui ActionBar 相当。実体は Ark Popover の再利用)。Root / Positioner / Content / SelectionTrigger / Separator / CloseTrigger の 6 anatomy パーツを持つ(action_bar.rs モジュール doc)。
- 開閉は Disclosure を埋め込んだ状態機械 ActionBar が管理する。選択件数から open を自動導出する糖衣 API は持たず、「選択操作 → 開閉状態の決定」は呼び出し側の責務とする(action_bar.rs「選択件数から open を導出する糖衣 API は持たない」節)。
- content に role="dialog"(非モーダル、aria-modal は付与しない)と aria-label(読み上げ用ラベル、呼び出し側が指定する必須引数)を固定付与する。参照基準(zag.js popover の content)に合わせイシュー #1647 で role="toolbar"(roving tabindex を伴わない不完全な適用だった)から是正した(**破壊的変更**、content 関数)。
- content は開状態のときのみ data-expanded 存在属性を出力し、tabindex="-1" を固定付与する(chakra autoFocus: false に対応。開時にフォーカスを自動移動しない。呼び出し側 attrs に tabindex があれば出力しない、content 関数)。
- close-trigger は呼び出し側 attrs に aria-label が無く、かつ children が空(可視テキストを持たないボタン)のときに限り既定値 "close"(zag.js popover の translations.closeTrigger 既定値、CLOSE_TRIGGER_ARIA_LABEL)を出力する(close_trigger 関数。children に可視テキストがあれば既定 aria-label は付与しない)。
- closed のとき positioner/content の双方に hidden 存在属性を付与し、SSR/no-JS でも閉状態を表現する(positioner/content 関数)。
- 参照基準に存在する data-placement/data-side(placement variant)は本実装のスコープ外(
docs/policy/intentional-non-adoption.md§3.25 規則 2、装飾・レイアウト計測は headless-ui へ持ち込まない)。外側クリックでの閉鎖(closeOnInteractOutside)は既定 false のまま opt-in 属性を持たない(選択操作のチェックボックス等が ActionBar の外側に存在するため、誤閉鎖を防ぐ安全側の判断)。
Anatomy
root
positioner
content
selection-trigger
separator
close-triggerAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
root(state) | OpenState | — | 開閉状態(Open/Closed)。data-state へ反映される。 |
content(label) | &str | — | role="dialog" の aria-label(選択操作バーの読み上げ名、必須引数)。 |
selection_trigger | fn | — | type="button" を固定付与するボタンパーツ。選択件数テキストは呼び出し側が children で渡す。 |
close_trigger(attrs) | Vec<(&str, &str)> | — | aria-label 未指定かつ children が空のときのみ既定値 "close" を出力する(CLOSE_TRIGGER_ARIA_LABEL)。呼び出し側が aria-label を指定するか children に可視テキストを渡せば上書き・不出力になる。 |
Data Attributes
| Part | Attribute | Observed Values |
|---|---|---|
content | data-expanded | (値なし) |
content | data-state | open |
positioner | data-state | open |
root | data-state | open |
Examples
CloseTrigger のみの最小構成
SelectionTrigger/Separator を省き、Content 直下に CloseTrigger だけを配置した例です。
自前 CSS の最小例
data-scope / data-part 属性セレクタと :focus-visible 擬似クラスで見た目を組み立てる最小例です。headless-ui 自体はスタイルを持ちません。
[data-scope="action-bar"][data-part="positioner"] {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
}
[data-scope="action-bar"][data-part="content"][data-state="open"] {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
[data-scope="action-bar"][data-part="close-trigger"]:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Escape | content が開いている間、バーを閉じる(fandhe-frontend-wasm-full の OverlayKind::ActionBar が配線。data-close-on-escape="false" で opt-out 可能)。 |
| Tab / Shift+Tab | ネイティブ要素(button)由来のフォーカス移動。開時に自動でフォーカスは移動しない(tabindex="-1"、chakra autoFocus: false に対応)。 |
WAI-ARIA
| Attribute | Description |
|---|---|
| role="dialog" | content に固定付与(非モーダル、aria-modal は付与しない)。aria-label とセットで出力される。参照基準(zag.js popover)に合わせイシュー #1647 で role="toolbar" から是正済み。 |
| data-expanded | content が開状態のときのみ出力される存在属性(zag.js popover の content と同じ語彙)。 |
| aria-label="close" | close-trigger の既定値。呼び出し側 attrs に aria-label が無く、かつ children が空(可視テキストを持たない)のときに限り出力される。呼び出し側が aria-label を指定するか children に可視テキストを渡せば出力されない。 |
| role="separator" / aria-orientation="vertical" | separator に固定付与(ActionBar のボタン列は横並びのため区切り線は縦向きになる。参照基準は素の div であり、a11y 上の superset として維持する意図的差分)。 |