コンテンツにスキップ
svelte-vitals
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

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'
  }
};