デベロッパー フィードバックのリクエスト: フォーカス グループ

Jacques Newman
Jacques Newman

公開日: 2026 年 3 月 5 日

focusgroup HTML 属性は、ロービング tabindex JavaScript を記述することなく、ツールバー、タブリスト、メニュー、リストボックスなどの複合ウィジェットにキーボードの矢印キー ナビゲーションを追加するための宣言型の手法として提案されています。1 つの属性で数百行のボイラープレートが置き換えられます。リリース前にフィードバックをお寄せください。

ぜひお試しになり、フィードバックをお寄せください

focusgroup は、Chrome、Edge、その他の Chromium ブラウザで、次のいずれかの方法で有効にすることで、本日よりお試しいただけます。

  1. ローカル テスト: ブラウザで about://flags ページを開き、[試験運用版のウェブ プラットフォーム機能] フラグを有効にします。または、--enable-blink-features=Focusgroup コマンドライン パラメータを使用して、コマンドラインからブラウザを起動します。
  2. オリジン トライアル: focusgroup オリジン トライアルに登録して、実際のユーザーを対象にサイトでテストします。

次に、インタラクティブなデモで、各パターンが実際に動作する様子を確認します。

ご意見をお聞かせください。フォーカス グループの問題を報告して、ご意見をお聞かせください。

これは、クロスブラウザの取り組みです。この提案は、Microsoft が OpenUI コミュニティ グループを通じて行い、Google が強く支持しています。API の形状は、フィードバックに基づいて変更される可能性があります。ここでは、focusgroup が解決する問題と API の仕組みについて詳しく説明します。

問題: 手動ロービング tabindex

ツールバー、タブリスト、メニュー、リストボックスを作成したことがあるなら、このコードのいずれかのバージョンを記述したことがあるでしょう。ARIA オーサリング プラクティス ガイド(APG)では、複合ウィジェットは 1 つのタブストップを表示し、ユーザーが矢印キーでアイテム間を移動できるようにすることを推奨しています。このパターンは「ロービング タブインデックス」と呼ばれます。多くの UI フレームワークでは、これをゼロから再実装しています。

<div role="toolbar" aria-label="Text formatting" id="toolbar">
  <button type="button" tabindex="0">Bold</button>
  <button type="button" tabindex="-1">Italic</button>
  <button type="button" tabindex="-1">Underline</button>
  <button type="button" tabindex="-1">Strikethrough</button>
</div>

ここから、開発者は矢印キーをリッスンしてフォーカスを移動し、すべての要素の tabindex 属性を調整する JavaScript を使用する必要があります。これは簡略化されたバージョンです。本番環境の実装では、次の処理も必要です。

  • 書き込みモードと RTL: コンテンツの方向に基づいて矢印キーの方向を調整します。
  • 最後にフォーカスされたアイテムの記憶: ユーザーがタブで戻ったときに、以前アクティブだったアイテムにフォーカスを復元します。
  • 無効化され非表示になっている項目: ナビゲーション中にスキップします。
  • 動的アイテム: アイテムが追加または削除されたときに、ロービング インデックスを更新します。

ReactAngular CDKFluent UI など、ほとんどの UI ライブラリには、このロジックの独自のバージョンが付属しています。プラットフォームのプリミティブにできるものを取得するために、多くの重複した作業が発生します。

解決策: focusgroup 属性

focusgroup を使用すると、同じツールバーは次のようになります。

<div focusgroup="toolbar" aria-label="Text formatting">
  <button type="button">Bold</button>
  <button type="button">Italic</button>
  <button type="button">Underline</button>
  <button type="button">Strikethrough</button>
</div>
斜体ボタンにフォーカスが当たっているメニューバー。

ライブで試す: Toolbar Pattern > Basic Toolbar。以上です。矢印キー ナビゲーション用の JavaScript がありません。手動の tabindex 管理は不要です。ブラウザが処理する内容は次のとおりです。

  • 矢印キーによるナビゲーション: 書き込みモードと方向性を尊重しながら、アイテム間を移動します。
  • 単一のタブストップ: 参加しているアイテムが 1 つのタブストップに自動的に折りたたまれます。デベロッパーは、非アクティブなアイテムに tabindex="-1" を設定する必要はありません。
  • 最後にフォーカスされたアイテムの記憶: ユーザーがフォーカス グループから離れて戻ってきたときに、フォーカスが離れる前にフォーカスされていたアイテムに復元されます。
  • ARIA セマンティクス: 汎用要素が使用されるときに選択された動作に基づいて、ブラウザが適切なロール(role="toolbar" など)を提供します。

デベロッパーは、押された状態の切り替え、メニューのオープン、選択の管理、カスタム コマンドなど、機能に固有のロジックのみを保持します。

API の概要

focusgroup 属性には、トークンのスペース区切りリストを指定します。最初のトークンは、常にウィジェット パターンを宣言する動作トークンです。オプションの修飾子トークンは focusgroup="<behavior> [inline|block] [wrap] [nomemory]" のようになります。

行動トークン

動作トークンは必須です(none を使用して上位のフォーカス グループをオプトアウトする場合を除く)。複合ウィジェット パターンを宣言し、それ以外に指定がない場合に適切なロールを推測できるようにします。トークンは、Aria Authoring Practices guide に記載されているパターンに従い、次の表に示されています。

動作 APG パターン 最小コンテナロール(適用時) 最小子ロール
(適用時)
デフォルトの修飾子
toolbar ツールバー ツールバー (なし) inline
tablist APG タブ tablist タブ inline wrap
radiogroup ラジオボタン グループ radiogroup ラジオ (なし)
listbox リストボックス リストボックス option (なし)
menu メニュー メニュー menuitem block wrap
menubar メニューバー menubar menuitem inline wrap
none なし × × なし

ロール マッピングの仕組みの詳細については、説明をご覧ください。

軸の制限(inlineblock

選択した動作にデフォルトの修飾子がない場合、4 つの矢印キーすべてがフォーカスの移動に使用されます。inline 修飾子または block 修飾子を使用すると、ナビゲーションを 1 つの論理軸に制限できます。

  • inline: フォーカス グループは、インライン軸の矢印キー(ほとんどの英語のコンテキストでは左右(水平、上から下))にのみ応答します。
  • block: focusgroup は、ブロック軸の矢印キー(ほとんどの英語のコンテキストでは上下)にのみ応答します(水平、上から下)。

軸の制限は CSS 論理プロパティと一致しており、書き込みモードと方向に合わせて自動的に調整されます。

ラップアラウンド ナビゲーション

デフォルトでは、フォーカスグループの端で矢印キー ナビゲーションが停止します。wrap 修飾子を追加して、最後のアイテムから最初のアイテムに(また、最初のアイテムから最後のアイテムに)ループします。動作がデフォルトで折り返しを行う場合は、nowrap 修飾子を使用してこの動作を無効にします。

ライブで試す: Tablist パターン > 折り返しありの水平 Tablist。 この例では、[FAQ] タブにフォーカスがあるときにユーザーが右矢印キーを押すと、フォーカスが [概要] タブに戻ります。

focusgroupstart 属性

focusgroupstart 属性は、フォーカスグループに初めてタブ移動したとき(またはメモリが無効になっている場合は毎回)にフォーカスを受け取る要素を指定します。

<div focusgroup="toolbar nomemory" aria-label="Entry point demo">
  <button type="button">First</button>
  <button type="button" focusgroupstart>Middle (Entry)</button>
  <button type="button">Last</button>
</div>
中央のボタンがフォーカスされているメニューバー。

focusgroupstart があり、nomemory 修飾子でメモリが無効になっているため、Tab キーと Shift+Tab キーの両方で [Middle (Entry)] に移動します。ライブで試す: ツールバー パターン > focusgroupstart を使用したエントリ ポイント

メモリを無効にする(nomemory)

デフォルトでは、focusgroups は最後にフォーカスされたアイテムを記憶し、Tab キーで再入力したときに復元します。フォーカスが常に固定のエントリ ポイントに戻るパターン(前のデモなど)では、focusgroup 属性で nomemory 修飾子を使用して無効にします。

この修飾子は、focusgroupstart のプログラムによる移動と組み合わせて、グループに入ったときにフォーカスされるアイテムを完全に制御することもできます。記憶された要素が使用できなくなった場合(削除、非表示、無効化、inert、フォーカス グループから除外された場合など)、メモリはクリアされます。

オプトアウト(focusgroup="none"

focusgroup="none" を使用すると、要素とそのサブツリーを祖先フォーカス グループの矢印ナビゲーションから除外できます。オプトアウトした要素とそのサブツリーは Tab キーで引き続きアクセスできますが、矢印キーではスキップされます。

<div focusgroup="toolbar" aria-label="Segmented toolbar">
  <button type="button">New</button>
  <button type="button">Open</button>
  <button type="button">Save</button>
  <span focusgroup="none">
    <button type="button">Help</button>
    <button type="button">Shortcuts</button>
  </span>
  <button type="button">Close</button>
  <button type="button">Exit</button>
</div>
ヘルプボタンとショートカット ボタンがグレー表示されているメニュー。

右矢印キーを使用すると、[新規作成]、[開く]、[保存]、[閉じる]、[終了] の順に移動し、[ヘルプ] ボタンと [ショートカット] ボタンは完全にスキップされます。ただし、ユーザーはヘルプ セクションにタブ移動して、これらのボタンにアクセスできます。ライブで試す: 追加のコンセプト > focusgroup="none" のオプトアウト セグメント

一般的なパターン

Tablist

タブ間の移動に矢印キーを使用するタブ コントロール。

<div focusgroup="tablist nomemory" aria-label="Sections">
  <button type="button" aria-selected="true" aria-controls="panel-overview" id="tab-overview" focusgroupstart>Overview</button>
  <button type="button" aria-selected="false" aria-controls="panel-features" id="tab-features">Features</button>
  <button type="button" aria-selected="false" aria-controls="panel-pricing" id="tab-pricing">Pricing</button>
  <button type="button" aria-selected="false" aria-controls="panel-faq" id="tab-faq">FAQ</button>
</div>
<div role="tabpanel" id="panel-overview" aria-labelledby="tab-overview" tabindex="0">...</div>
<div role="tabpanel" id="panel-features" aria-labelledby="tab-features" tabindex="0">...</div>
<div role="tabpanel" id="panel-pricing" aria-labelledby="tab-pricing" tabindex="0">...</div>
<div role="tabpanel" id="panel-faq" aria-labelledby="tab-faq" tabindex="0">...</div>
[概要] タブにフォーカスがあります。

ライブで試す: Tablist Pattern > Horizontal Tablist with Wrapping

注目すべき点:

  • focusgroupstart 属性は [選択済み] タブにあるため、フォーカスは常にそこに移動します。
  • nomemory 修飾子は、ユーザーが以前に別のタブにフォーカスしていた場合でも、再入力時に常に選択されたタブに移動するようにします。
  • inline 修飾子は、矢印キーのナビゲーションを左右のキーのみに制限します。これは、APG タブ パターンで説明されている想定される動作と一致しています。
  • wrap 修飾キーを使用すると、すべてのタブで矢印キーを連続して使用できます。
  • 簡潔にするため省略したデベロッパー コードは、実際の選択を処理します。つまり、aria-selected を更新し、パネルの可視性を切り替え、選択の変更時に focusgroupstart 属性を移動します。

上下の矢印ナビゲーションを備えたシンプルな縦型メニュー。

<div focusgroup="menu" aria-label="File actions" class="menu-vertical">
    <button type="button" class="menu-item">New</button>
    <button type="button" class="menu-item">Open…</button>
    <button type="button" class="menu-item">Save</button>
    <button type="button" class="menu-item">Exit</button>
</div>
[開く] メニュー項目にフォーカスが設定された縦型メニュー。

ライブで試す: メニューとメニューバーのパターン > シンプルな縦型メニューblock 修飾子を使用すると、上下の矢印キーでのみアイテムを移動できます。左右の矢印キーは、定義した動作(サブメニューを開くなど)に自由に使用できます。ネストされたサブメニューを含むメニューバーの場合、各レベルは独立した focusgroup になります。ライブで試す: メニューとメニューバーのパターン > ポップオーバー サブメニュー付きのメニューバー

<ul role="menubar" focusgroup="menubar"
     aria-label="Application Menu" class="menubar">
    <li role="none">
        <button role="menuitem" type="button" class="menubar-item"
             aria-haspopup="menu" aria-expanded="false"
             popovertarget="filemenu">File</button>
        <ul role="menu" focusgroup="menu"
             id="filemenu" popover aria-label="File submenu" class="submenu">
            <li role="none"><button type="button" class="submenu-item"
                 autofocus>New</button></li>
            <li role="none"><button type="button" class="submenu-item">Open</button></li>
            <li role="none"><button type="button" class="submenu-item">Save</button></li>
        </ul>
    </li>
    <!-- More menu items... -->
</ul>
コピー項目にフォーカスが当たっているプルダウン メニュー。

ライブで試す: メニューとメニューバーのパターン > ポップオーバー サブメニュー付きのメニューバー。メニューバーは左右の移動に inline 修飾子を使用しますが、サブメニューは上下の移動に block 修飾子を使用します。ネストされたフォーカス グループは完全に独立しているため、互いに干渉しません。

Radiogroup

矢印キー ナビゲーションと完全なスタイリング制御を備えたカスタム ラジオボタン グループ。

<div focusgroup="radiogroup" aria-label="Favorite color">
  <span aria-checked="false" tabindex="0">Red</span>
  <span aria-checked="false" tabindex="0">Green</span>
  <span aria-checked="true" tabindex="0" focusgroupstart >Blue</span>
  <span aria-checked="false" tabindex="0">Purple</span>
</div>
ラジオボタン グループで「青」がフォーカスされている。

ライブで試す: ラジオボタン グループ パターン > 比較: ネイティブとフォーカス グループ

focusgroup 属性は矢印キー ナビゲーションを処理しますが、選択コードは実装する必要があります。このデモでは、JavaScript コードがチェック状態を管理します(aria-checked 属性を使用)。

主なコンセプト

フォーカス グループ アイテムの参加

focusgroup が有効な動作に設定されている要素のすべての順次フォーカス可能な子孫は、そのフォーカス グループに参加していると見なされます。つまり、tabindex が負の要素は考慮されませんが、<button> などのネイティブにフォーカス可能な要素や、負でない tabindex を指定した要素は考慮されます。

タブ位置

tabindex 値を管理する必要はありません。複数の子孫が自然にタブ移動可能である場合(複数の <button> 要素など)でも、focusgroup はそれらを単一のタブストップに折りたたみます。ブラウザは、どの項目がタブ移動可能かを常に処理します。ライブで試す: ツールバー パターン > tabindex の管理は不要

最後にフォーカスされたメモリ

デフォルトでは、ユーザーが Tab キーを押してフォーカス グループから移動し、その後 Tab キーを押して戻ると、フォーカスは最後にフォーカスされたアイテムに戻ります。これは、ユーザーが位置を見失わないようにするために、大きなリストやツールバーで重要になります。フォーカスを常に最初の要素に戻したい場合や、focusgroupstart を使用して最初にフォーカスされる要素を制御する場合は、nomemory 修飾子を使用してこの動作を無効にします。

ネストされたフォーカス グループ

各 focusgroup 宣言は独立したスコープを作成します。ネストされた focusgroup は、祖先の矢印ナビゲーションから自動的にオプトアウトします。Tab キーを使用してフォーカス グループ間を移動し、矢印キーを使用して現在のフォーカス グループ内を移動します。ライブで試す: その他のコンセプト > ネストされたフォーカス グループ

Shadow DOM のサポート

focusgroup は、デフォルトで Shadow DOM の境界を越えて適用されます。シャドーホストで宣言された focusgroup には、そのホストのシャドーツリー内のフォーカス可能な要素が含まれます。オプトアウトする場合は、コンポーネントのシャドウ ツリー内で focusgroup="none" を使用できます。

キーの競合処理

フォーカス グループ内の要素(<input><textarea> などのコントロール)は、独自の目的で矢印キーを使用します。フォーカス グループのナビゲーション キーとネイティブ要素の矢印キーの動作が競合している場合:

  • 矢印キーはインタラクティブ要素によって使用され(テキスト カーソルの移動など)、focusgroup は干渉しません。
  • Tab キーまたは Shift+Tab キーはデフォルトのエスケープ メカニズムを提供し、ユーザーは Tab ナビゲーションを使用してフォーカス グループに「再入力」できます。

これらのエスケープ動作は、実際のキーの競合がある場合にのみ適用されます。競合しない軸は影響を受けません。keydown イベントで preventDefault() を呼び出して、特定の要素のフォーカス グループの矢印キーの動作をオーバーライドすることもできます。つまり、この動作を損なうことなく、focusgroup 内に input と textarea を含めることができます。

フォーカス グループに参加している独自の要素にキー ハンドラを追加する場合は、ユーザーがグループの残りの部分にアクセスできるように、同様のエスケープ メカニズムを提供してください。

深い子孫の検出

Focusgroup アイテムは、focusgroup コンテナの直接の子である必要はありません。

ブラウザは、ネストされた focusgroup 内にあるか、focusgroup="none" でオプトアウトされていない限り、すべての順次フォーカス可能な子孫(非負の tabindex)が focusgroup に参加すると見なします。

<div focusgroup="toolbar" aria-label="Nested wrappers">
  <div>
    <span>
      <button type="button">Alpha</button>
    </span>
    <span>
      <button type="button">Beta</button>
    </span>
    <span>
      <button type="button">Gamma</button>
    </span>
  </div>
</div>

ボタンが <div><span> ラッパー内にネストされていても、矢印キーによるナビゲーションは機能します。フラットリストの要件はないため、スタイリング用のラッパー要素は問題ありません。

ライブで試す: その他のコンセプト > 深い子孫

reading-flow プロパティとの統合

順次ナビゲーション(Tab キー)と方向ナビゲーション(矢印キー)の両方で、CSS の reading-flow プロパティが存在する場合は、DOM のソース順ではなく、視覚的な読み取り順に従います。

これにより、矢印キー ナビゲーションが、ユーザーが画面に表示するレイアウトと一致します。

<div focusgroup="toolbar" aria-label="Visual order"
     style="display: flex; flex-direction: row-reverse; reading-flow: flex-visual;">
  <button type="button">A (DOM first)</button>
  <button type="button">B (DOM second)</button>
  <button type="button">C (DOM third)</button>
</div>
項目 A にフォーカスがあります。

DOM の順序は A、B、C ですが、レイアウトで flex-direction: row-reverse が使用されているため、視覚的な順序は C、B、A になります。ただし、コードで reading-flow: flex-visual も使用しているため、読み取り順序は A、B、C に戻り、focusgroup はこの順序と一致します。

Tab キーを押すと、まず C にフォーカスが移動し、→ キーを押すと、B、A の順にフォーカスが移動します。ライブで試す: 追加のコンセプト > CSS 読み取りフローの統合

ユーザー補助

ARIA ロールの推論

フォーカス グループでは、ブラウザは動作トークンを使用して、コンテナとその参加アイテムの両方の最小ロールを推測します。つまり、汎用ロールを持つ要素に focusgroup 属性が設定されている場合、選択された動作に基づいて正しいロールが適用されます。汎用ロールを持つ要素の参加アイテム、または指定したロールを持たないボタンのロールは、それに応じて推測されます。たとえば、次の HTML の場合:

<div focusgroup="tablist">
  <button>Tab 1</button>
  <button>Tab 2</button>
  <button>Tab 3</button>
</div>

ボタンにロールが定義されていなくても、次のアクセシビリティ ツリーが作成されます。

+   tablist
  |
  +   tab
  |
  +   tab
  |
  +   tab

ロールを直接設定することで、動作を常に制御できます。

ユーザー補助に関する考慮事項

フォーカス グループの作成時に選択した動作を尊重するようにしてください。

フォーカス グループの使用状況は、指定した動作にできるだけ近いものにする必要があります。これは、ユーザー補助ツールを使用するユーザーがコンテンツをナビゲートし、カスタム コントロールを使用できるようにするために重要です。

ロール推論は適切なデフォルトを提供しますが、汎用ロール以外のロールを持つ要素を使用する場合は、それらの要素が提供する機能に適切なロールが設定されていることを確認してください。

focusgroup を使用する場合は、ユーザーが矢印キーでスクロールしてコンテンツを表示できるようにする必要があることを覚えておいてください。キーボード ユーザーがページ上のコンテンツを読み取ってアクセスできる方法が常に存在する必要があります。

特徴検出

focusgroup をブラウザ全体で完全にサポートする前に使用を開始するには、JavaScript で focusgroup のサポートを検出します。

if ('focusgroup' in HTMLElement.prototype) {
  // focusgroup is supported.
} else {
  // fall back to manual roving tabindex.
}

まとめ

focusgroup 属性は標準化団体で検討が進められており、Chromium でのプロトタイプの構築と API の改良が積極的に進められています。

ぜひお試しいただき、Open-UI GitHub 公開バグトラッカーでフォーカス グループの問題を報告してください。特に、以下の点についてのご意見をお待ちしております。

  • 構築するパターンに API サーフェスが適しているか。
  • 見落としているパターンやシナリオはないか。
  • focusgroup 属性が許可されない要素はありますか?
  • アクセシビリティ ストーリーはユースケースでどのように機能しますか?

ウェブのキーボード ナビゲーションの改善にご協力いただきありがとうございます。

その他の情報

focusgroup の復活にご協力いただいた Mason Freed 氏、Sara Higley 氏、Scott O'Hara 氏、および Open-UI コミュニティの皆様に感謝いたします。