a11y/no-autofocus · ダイアログ外の autofocus
autofocus はページ読み込み時にユーザーの意図と無関係にフォーカスを移動させます。スクリーンリーダーのユーザーは組み立てていたページの文脈を失い、キーボードのユーザーはページの途中に放り出されます。
重大度: warning · カテゴリ: a11y
チェック内容
リテラルな autofocus 属性(素の属性または文字列値)を持つ要素を検出します。ただしその要素が <dialog> 自身であるか、同じコンポーネントテンプレート内で <dialog> または popover コンテナの中にある場合は除きます。
<input autofocus placeholder="Search" />
検出しないもの:
- 式による
autofocus={expr}。式はfalseになりえるため、描画される属性は静的に判定できません。 <dialog>要素自身、またはその子孫のautofocus。ダイアログのフォーカス処理はページ読み込み時ではなく表示時に走り、autofocusを持つ子孫を尊重します。これがこの属性の正しい使い方です。popover属性を持つ要素の子孫。形式(素・リテラル・式)を問いません。popover のフォーカス処理はダイアログと同様に振る舞い、式は実際の popover 値に解決されうるため、正規のパターンを検出してしまうよりも除外を寛大に保ちます。- スプレッド属性経由で供給される
autofocus。静的解析の範囲外です。
なぜ重要か
autofocus はページ読み込み時に、ユーザーの意図と無関係に、ターゲットより前のすべてを飛ばしてフォーカスを移動させます。スクリーンリーダーのユーザーは組み立てていたページの文脈、つまり最初に聞くはずだったタイトル・見出し・ランドマークを失い、キーボードのユーザーは何が前にあったのか分からないままページの途中に放り出されます。<dialog> や popover の中では意味が異なります: フォーカスはコンテナの表示時に移動し、それはまさに WAI-ARIA のダイアログパターンが要求するものです。それ以外の場所では、ほぼ確実にユーザビリティのバグです。
修正方法
属性を削除し、フォーカスはページ先頭から始めさせます。
<input placeholder="Search" />
コントロールがダイアログや popover に属するなら、そのコンテナの中に移動して autofocus を残してください。それがサポートされたパターンです。ユーザー操作に応じたフォーカス移動はイベントハンドラで行います。
制限事項
dialog/popover の除外が見えるのは同じコンポーネントテンプレート内の祖先だけで、祖先チェーンはレンダリング位置が字句的でないすべての構造(コンポーネント境界、{#snippet} 本体、<svelte:element>、カスタム要素や未知のタグ、{@html}、{@render}、<slot>)で途切れます。途切れた先ではダイアログを証明できないため、親が <dialog> の中にレンダリングするコンポーネント内の autofocus は既知の偽陽性です。その要素を <!-- svelte-vitals-disable-next-line a11y/no-autofocus --> で黙らせてください。
Svelte コンパイラとの重複
コンパイラは同様のマークアップに a11y_autofocus を警告し、同じ <dialog> の除外を持ちます。この重複は意図的です: コンパイラの警告はビルドログに流れるだけで、スコアもゲートも抑制もしません。一方このルールはヘルススコアに反映され、svelte-vitals-disable-next-line を尊重し、--fail-on で CI を失敗させられます。
意図的な相違点が4つあります。
- 式による
autofocus={expr}はコンパイラが警告しますがこのルールは沈黙します。式はfalseになりえます。 <dialog>の深い子孫(例:<dialog><div><input autofocus /></div></dialog>)はコンパイラが警告しますが、これは直近の要素祖先しか確認しないためです。このルールはチェーン全体を辿って正しく通します。<svelte:element>を挟んだ祖先はコンパイラが沈黙し、このルールは途切れたチェーンとして報告します: 包含関係が判定できないときのデフォルトが逆です。- popover コンテナはこのルールでは属性を除外しますが、コンパイラでは除外しません。
モードによる違い
ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。
無効化
ページ読み込み時の autofocus が本当に意図したものなら(検索専用ページ、ログインフォームなど)、<!-- svelte-vitals-disable-next-line a11y/no-autofocus --> で個別の要素を黙らせるか、ルールを無効化します。
export default {
rules: {
'a11y/no-autofocus': 'off'
}
};