Tabindex の使用

tabindex による DOM 順序の変更

Dave Gash
Dave Gash
Meggin Kearney
Meggin Kearney

ネイティブ要素の DOM 位置で提供されるデフォルトのタブ順序は便利ですが、タブ順序を変更したくなることもあるでしょう。また、HTML 内の物理的に動く要素が必ずしも最適であるとは限りません。また、現実的な解決策でもありません。このような場合は、tabindex HTML 属性を使用して、要素のタブ位置を明示的に設定できます。

対応ブラウザ

  • 1
  • 12
  • 1.5
  • 4 以下

ソース

tabindex はどの要素にも適用できますが、すべての要素で役立つとは限りません。また、特定の整数値の範囲を取ります。tabindex を使用すると、フォーカス可能なページ要素の明示的な順序を指定し、本来フォーカス不可能な要素をタブオーダーに挿入し、要素をタブオーダーから削除できます。次に例を示します。

tabindex="0": 要素を自然なタブオーダーに挿入します。Tab キーを押すと要素をフォーカスでき、focus() メソッドを呼び出すと要素をフォーカスできます。

<custom-button tabindex="0">Press Tab to Focus Me!</custom-button>

フォーカスするには Tab キーを押してください

tabindex="-1": 自然なタブオーダーから要素を削除しますが、focus() メソッドを呼び出して要素をフォーカスできます。

// TODO: DevSite - Code sample removed as it used inline event handlers

// TODO: DevSite - インライン イベント ハンドラを使用したため、コードサンプルを削除する

tabindex="5": tabindex が 0 より大きい場合、要素は自然なタブオーダーの前にジャンプします。tabindex が 0 より大きい要素が複数ある場合、0 より大きい最小値から順番に増えていきます。0 より大きい tabindex の使用は、アンチパターンとみなされます。

<button>I should be first</button>
<button>And I should be second</button>
<button tabindex="5">But I jumped to the front!</button>

これは特に、ヘッダー、画像、記事のタイトルなどの非入力要素に当てはまります。このような要素に tabindex を追加すると、逆効果になります。可能な場合は、DOM シーケンスが論理的なタブオーダーになるようにソースコードを配置することをおすすめします。tabindex を使用する場合は、ボタン、タブ、プルダウン、テキスト フィールドなどのカスタム インタラクティブ コントロール(ユーザーが入力を提供する要素)に限定します。

スクリーン リーダーには tabindex がないため、スクリーン リーダーのユーザーが重要なコンテンツを見逃す心配はありません。画像のようにコンテンツが非常に重要であっても、ユーザーが操作できないコンテンツであれば、フォーカス可能にする必要はありません。alt 属性を適切にサポートしていれば、スクリーン リーダーのユーザーは画像の内容を理解できます。これについては後ほど説明します。

ページレベルでフォーカスを管理する

ここでは、tabindex が有用であるだけでなく必須のシナリオを示します。すべてのコンテンツが同時に表示されるわけではない、さまざまなコンテンツ セクションを含む堅牢な 1 ページを構築しているとします。このようなページでは、ナビゲーション リンクをクリックすると、ページを更新せずに表示されるコンテンツが変更されることがあります。

このような場合は、選択したコンテンツ領域を特定し、自然なタブオーダーで表示されないように tabindex を -1 に設定して、focus メソッドを呼び出します。フォーカスの管理と呼ばれるこの手法では、ユーザーが知覚するコンテキストとサイトのビジュアル コンテンツの同期が維持されます。

コンポーネントへのフォーカスの管理

ページ上の何かを変更するときのフォーカスの管理は重要ですが、場合によっては、コントロール レベルでフォーカスを管理する必要があります。たとえば、カスタム コンポーネントを作成する場合です。

ネイティブの select 要素について考えてみましょう。基本フォーカスは受け取ることができますが、移動後は矢印キーを使用して追加機能(選択可能なオプション)を表示できます。カスタムの select 要素を作成する場合は、これらの同じ種類の動作を公開して、主にキーボードを使用しているユーザーが引き続きコントロールを操作できるようにすることをおすすめします。

<!-- Focus the element using Tab and use the up/down arrow keys to navigate -->
<select>
    <option>Aisle seat</option>
    <option>Window seat</option>
    <option>No preference</option>
</select>

実装するキーボードの動作を把握するのは難しい場合がありますが、参考になるドキュメントがあります。Accessible Rich Internet Applications(ARIA)Authoring Practices ガイドには、コンポーネントの種類と、コンポーネントでサポートされるキーボード アクションの種類が記載されています。ARIA については後で詳しく説明しますが、ここでは、このガイドを使用して、新しいコンポーネントにキーボード サポートを追加する方法について説明します。

ラジオボタンのセットに似た新しいカスタム要素を、独自の外観と動作で作成している場合があります。

<radio-group>
    <radio-button>Water</radio-button>
    <radio-button>Coffee</radio-button>
    <radio-button>Tea</radio-button>
    <radio-button>Cola</radio-button>
    <radio-button>Ginger Ale</radio-button>
</radio-group>

必要なキーボード サポートの種類を確認するには、ARIA オーサリング プラクティス ガイドをご覧ください。セクション 2 には、設計パターンのリストが含まれています。このリストには、新しい要素に最も近い既存のコンポーネントであるラジオグループの特性の表があります。

この表からわかるように、サポートする必要がある一般的なキーボード動作の一つは、上下左右の矢印キーです。この動作を新しいコンポーネントに追加するには、tabindex の移動という手法を使用します。

ラジオボタンに関する W3C 仕様の抜粋。

tabindex の移動は、現在アクティブな子を除くすべての子に対して tabindex を -1 に設定することで機能します。

<radio-group>
    <radio-button tabindex="0">Water</radio-button>
    <radio-button tabindex="-1">Coffee</radio-button>
    <radio-button tabindex="-1">Tea</radio-button>
    <radio-button tabindex="-1">Cola</radio-button>
    <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

次に、コンポーネントはキーボード イベント リスナーを使用して、ユーザーがどのキーを押しているかを判断します。このとき、以前フォーカスされていた子の tabindex を -1 に設定し、フォーカスされる子の tabindex を 0 に設定して、その子に対して focus メソッドを呼び出します。

<radio-group>
    // Assuming the user pressed the down arrow, we'll focus the next available child
    <radio-button tabindex="-1">Water</radio-button>
    <radio-button tabindex="0">Coffee</radio-button> // call .focus() on this element
    <radio-button tabindex="-1">Tea</radio-button>
    <radio-button tabindex="-1">Cola</radio-button>
    <radio-button tabindex="-1">Ginger Ale</radio-button>
</radio-group>

ユーザーが最後の子(フォーカスを移動する方向によっては最初)の子に到達すると、ループして最初(または最後)の子に再びフォーカスします。

以下の完成した例をお試しください。DevTools で要素を調べて、tabindex が次のラジオボタンに移動することを確認します。

コーヒー お茶 コーラ ジンジャーエール

// TODO: DevSite - インライン イベント ハンドラを使用したため、コードサンプルを削除する

この要素の完全なソースは GitHub で確認できます。

モーダルとキーボード トラップ

フォーカスを管理しているときに、抜け出せない状況に陥ることがあります。フォーカスの管理とタブの動作のキャプチャを試みるが、完了するまでユーザーが離れることを防ぐオートコンプリート ウィジェットについて考えてみましょう。これはキーボード トラップと呼ばれ、ユーザーにとって非常に不満です。ウェブ AIM チェックリストのセクション 2.1.2 では、この問題に対処しており、キーボードのフォーカスを 1 つの特定のページ要素でロックまたはトラップしないようにすることが規定されています。ユーザーがキーボードのみを使用して、すべてのページ要素間を移動できるようにする必要があります。

奇妙なことに、モーダル ウィンドウのように、この動作が実際には望ましい場合があります。通常は、モーダルが表示されたときに、ユーザーがその背後にあるコンテンツにアクセスできないようにします。ページを視覚的に覆うオーバーレイを追加しても、キーボードのフォーカスが誤ってモーダルの外に移動することにはなりません。

作業内容を保存するようユーザーに求めるモーダル ウィンドウ。

このようなインスタンスでは、一時的なキーボード トラップを実装して、モーダルが表示されている間だけフォーカスをトラップし、モーダルを閉じる際に以前にフォーカスされたアイテムにフォーカスを復元できます。

<dialog> 要素など、これを簡単にする方法に関する提案もありますが、ブラウザは広くサポートされていません。

<dialog> の詳細については、こちらの MDN 記事をご覧ください。モーダル ウィンドウの詳細については、こちらのモーダルの例をご覧ください。

いくつかの要素を含む div で表されるモーダル ダイアログと、背景オーバーレイを表す別の div について考えてみましょう。この状況で一時的なキーボード トラップを実装するために必要な基本的な手順を見ていきましょう。

  1. document.querySelector を使用して、モーダル div とオーバーレイ div を選択し、その参照を保存します。
  2. モーダルが開いたら、モーダルが開いたときにフォーカスされた要素への参照を保存し、その要素にフォーカスを戻せるようにします。
  3. keydown リスナーを使用して、モーダルが開いているときに押されたキーを取得します。また、背景オーバーレイのクリックをリッスンし、ユーザーがクリックしたときにモーダルを閉じることもできます。
  4. 次に、モーダル内でフォーカス可能な要素のコレクションを取得します。最初と最後のフォーカス可能な要素は「監視員」として機能し、モーダル内に留まるためにフォーカスを前後にループさせるタイミングを知らせます。
  5. モーダル ウィンドウを表示して、最初のフォーカス可能な要素をフォーカスします。
  6. ユーザーが Tab または Shift+Tab を押したときに、フォーカスを前後に動かし、必要に応じて最後または最初の要素でループさせます。
  7. ユーザーが Esc を押した場合は、モーダルを閉じます。ユーザーは特定の閉じるボタンを検索せずにモーダルを閉じることができるため、非常に便利です。また、マウスを使用しているユーザーにもメリットがあります。
  8. モーダルを閉じると、モーダルと背景オーバーレイが非表示になり、以前に保存したフォーカスのある要素にフォーカスが復元されます。

この手順により、誰でも効果的に使用でき、ストレスを感じないモーダル ウィンドウが完成します。

詳細については、こちらのサンプルコードをご覧ください。また、完成したページの実際の例を確認することもできます。