a11y/interactive-nesting · 操作要素の中に操作要素がネストしている
インタラクティブな要素を、別のインタラクティブな要素の内側に置いてはいけません。
重大度: warning · カテゴリ: a11y
チェック内容
インタラクティブな要素(<button>、<input>、リテラルなインタラクティブ role など)が、別のインタラクティブなコンテナの内側にネストされている場合を検出します。コンポーネントのソースを解析します。
このルールが「コンテナ」として監視するのは、次の 3 種類の要素だけです。
<a href="…">。hrefのない<a>はコンテナとみなしません。<button>。- ARIA が children-presentational と定めるリテラルなロールを持つ要素(ユーザーエージェントは子孫をアクセシビリティ API に公開すべきでない、とされています)。
button、checkbox、radio、switch、tab、menuitemcheckbox、menuitemradio、option、slider、scrollbar。linkもコンテナとして扱いますが、これは ARIA が children-presentational と定めているからではなく(定めていません)、<a href>が HTML のコンテンツモデルで負っているのと同じネスト制限を、このルールがlinkにも適用しているからです。自前のコントロールを含むのが正しいロールはコンテナではありません:role="gridcell"がボタンを含むのは grid パターンの標準形ですし、ARIA 1.1 のrole="combobox"は自身の<input>を囲む形です。
これらのコンテナが開いている間にインタラクティブな要素が現れると検出されます。例:
<a href="/x">
<button>Go</button>
</a>
検出しないもの:
- インタラクティブ性の根拠が
tabindexだけの子孫で、そのtabindexが負の場合(例:<div tabindex="-1">)。タブ順から外れており、キーボードフォーカスを奪い合いません。<button tabindex="-1">のようにネイティブにインタラクティブな子孫は検出されたままです。クリックは依然として可能で、コンテナのコンテンツモデルはタブ順に関係なくこれを禁じているためです。 hrefのない<a>の子孫。hrefを持たない<a>はそれ自体インタラクティブではありません。- コンポーネントをまたいだネスト(例:
<a href>の内側にレンダリングされる子コンポーネントの中の<button>)。このルールは 1 つのコンポーネント自身のテンプレートしか見ないため、この変種は既知の非対応事項です。 - 名前空間は追跡していません。
<svg>内の<a href>は HTML の<a>として分類されるので、<button>を包む SVG のリンクはインタラクティブ要素のネストとして報告されます。これはパーサの近似であって SVG の制約ではありません。SVG 自身が禁じるのは SVG リンクのネストだけです。ただし HTML ルールの読者も望まないパターンなので、この報告は残しています。
なぜ重要か
キーボードや支援技術のユーザーは、DOM 上の位置ではなくコントロール単位で操作します。あるコントロールが別のコントロールの内側にネストされていると、アナウンスも操作も一貫しません。クリックや Enter がどちらの要素を作動させるかはブラウザによって割れ、スクリーンリーダーによってその組をどう提示するかも異なります。外側が <a href> や <button> の場合は不正な HTML でもあります(これらのコンテンツモデルはインタラクティブな子孫を明確に禁じています)。<div> に role="button" を付けたようなロールベースのコンテナはこのコンテンツモデルの規定の対象外ですが、操作モデルの壊れ方は同じです。
修正方法
各インタラクティブなコントロールが、互いに子孫ではなく兄弟になるようマークアップを組み直します。
<div>
<a href="/x">Go to x</a>
<button>Extra action</button>
</div>
モードによる違い
ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。
無効化
ネストが意図的で、別の方法(pointer-events と合成フォーカストラップなど)で対処済みの場合は、<!-- svelte-vitals-disable-next-line a11y/interactive-nesting --> で個別の要素を抑制するか、ルールを無効化してください。
export default {
rules: {
'a11y/interactive-nesting': 'off'
}
};