a11y/positive-tabindex · 正の tabindex
0 より大きい tabindex はその要素をページ内の自然順のすべての要素より前に置き、たった1つの tabindex="1" がページ全体のキーボードナビゲーション順を組み替えてしまいます。
重大度: warning · カテゴリ: a11y
チェック内容
リテラルの tabindex 属性の値が 0 より大きい有限数にパースされる要素を検出します。
<div tabindex="1">Jumps the tab queue</div>
検出しないもの:
tabindex="0"。要素は自然なタブ順に加わります。tabindex="-1"。プログラムからのみフォーカス可能になります。- 式による
tabindex={expr}。描画される値は静的に判定できません。 - 空の
tabindex。不正な HTML で、ブラウザに無視されます。 Number()でパースできない値、たとえばtabindex="1abc"。ブラウザは先頭の整数をパースします(その要素のtabIndexは実際に 1 になります)が、Svelte コンパイラ自身のチェックも同じ形でスキップします。ルールがコンパイラに従う理由は下の重複の節を参照してください。
なぜ重要か
正の tabindex を持つ要素は、ページ内の自然順のすべての要素より前に来ます。そのためたった1つの tabindex="1" がドキュメント全体のキーボードナビゲーションを組み替えてしまい、被害は膨らみ続けます: 後から追加された正の tabindex を持たない要素は、視覚上どこにあってもすべてその後ろに並びます。タブ順は視覚上の順序から乖離しえて、WCAG 2.4.3 (Focus Order) が求める意味のあるフォーカス順が壊れます。安全な値は 0 と -1 だけというのが広く合意されたガイダンスで、正の値が意図的かつ正しいことはまずありません。
修正方法
要素を DOM 順に配置して自然なタブ順に任せます。tabindex="0" はキーボードで到達させたい非インタラクティブ要素(たとえばスクロール領域)に限って使います。
<div tabindex="0" role="region" aria-label="Release notes" class="scroll-box">…</div>
(クリックできるものにはネイティブの <button> を使ってください。それ自体でタブ順に加わります。)
プログラムからフォーカスする要素(スキップリンクのターゲットやダイアログなど)には tabindex="-1" を使います。
<div tabindex="-1" bind:this={panel}>Focused from code</div>
制限事項
対象になるのは静的に名前が決まる要素のリテラルな tabindex 値だけで、カスタム要素も含まれるため、<my-widget tabindex="1"> も検出されます。式による tabindex、それを供給しうるスプレッド属性、<svelte:element> による動的タグは静的解析の範囲外のため検出されません。
Svelte コンパイラとの重複
コンパイラは同じマークアップに a11y_positive_tabindex を警告します。この重複は意図的です: コンパイラの警告はビルドログに流れるだけで、スコアもゲートも抑制もしません。一方このルールはヘルススコアに反映され、svelte-vitals-disable-next-line を尊重し、--fail-on で CI を失敗させられます。
2つのチェックは値のパース(Number() ベース)を共有しており、この整合の維持こそが、ルールが HTML の先頭整数パースを実装しない理由です。意図的な相違点が2つ残っており、どちらもこのルールはブラウザ側に付きます: 素の <div tabindex> はコンパイラが警告しますがこのルールは沈黙します(ブラウザはその要素に tabIndex -1 を与えます)。また tabindex="Infinity" のような非有限値やオーバーフローする数値もコンパイラは警告しますがこのルールは沈黙します(ブラウザはその値を無視します)。
モードによる違い
ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。
無効化
正の値が本当に意図したものなら、<!-- svelte-vitals-disable-next-line a11y/positive-tabindex --> で個別の要素を黙らせるか、ルールを無効化します。
export default {
rules: {
'a11y/positive-tabindex': 'off'
}
};