WCAG 達成基準 · Level AAA

WCAG 2.4.12: フォーカスの非非表示(強化)

WCAG 2.4.12 では、UI コンポーネントがキーボードフォーカスを受け取ったとき、そのコンポーネントの一部も制作者が作成したコンテンツによって隠されてはならないと求めている — フォーカスされた要素は完全に見えていなければならない。強化された (AAA) 基準は、AA における一部のみ可視でよいという許容をなくし、キーボードユーザーが常にフォーカス位置を正確に確認できるようにしている。

この規定の意味

WCAG 2.4.12 — フォーカスの非非表示(拡張) — は、WCAG 2.4.11(フォーカスの非非表示、AA)のAAAに相当する基準です。AAの達成基準では、フォーカスされたコンポーネントが一部見えていればよいのに対し、AAAの達成基準では、フォーカスされたコンポーネントが完全に見えていることが求められます — キーボードフォーカスを受け取ったとき、その一部たりとも作成者が用意したコンテンツによって隠されていてはなりません。

実務的には、ユーザーがリンク、ボタン、フォームフィールド、カスタムウィジェットなどのインタラクティブ要素にTabキーで移動したとき、その要素の境界領域全体が、スティッキーヘッダー、固定フッター、モーダルオーバーレイ、クッキーバナー、チャットウィジェット、その他作成者がページ上に配置したコンテンツによって覆われていない必要がある、ということです。この規定は特に作成者が用意したコンテンツを対象としています。W3Cは、ユーザー自身がフォーカスインジケーターを隠すように動かしたコンテンツ — 例えば、ユーザーがフォーカスされた要素の前にドラッグしたフローティングパネル — については明示的に例外としています。その場合、作成者には責任がありません。

2.4.12に合格するには、フォーカスを受け取った際、フォーカスされたコンポーネント全体がビューポート内に見えており、ページ作成者が制御するスティッキー、固定、または絶対配置の要素によって覆われていないことが必要です。フォーカスされた要素の可視境界の一部でも、そのようなオーバーレイの背後に隠れている場合は不合格となります — フォーカスリングやコンポーネント自体の1ピクセルでもクリップされていれば、AAAレベルでは失敗と見なされます。

2.4.12が対象としないものを理解しておくことも重要です。この基準は特定のフォーカスインジケーターのスタイルを義務付けるものではありません(それは2.4.11と2.4.7で扱われます)。フォーカスインジケーターに最低コントラスト比を求めるものでもありません(2.4.13で扱われます)。この基準が扱うのは、ページ上のフォーカスされた要素と他のコンテンツとの空間的関係、つまりCSSのfixedやsticky配置によって最もよく発生するレイヤリングの問題です。

影響を受けるHTML要素には、フォーカス可能またはTab移動可能なあらゆる要素が含まれます。<a><button><input><select><textarea><details>tabindexを持つ要素、ARIAロールで構築されたカスタムインタラクティブウィジェットなどです。この達成基準は、iframe、ダイアログ、シングルページアプリケーションのルート遷移を含む、すべてのブラウジングコンテキストに適用されます。

なぜ重要か

キーボードナビゲーションは、幅広いユーザーにとって主要なアクセス手段です。ALS、多発性硬化症、脳性まひ、反復性ストレス障害などの状態で生活している人を含む運動障害のある人は、マウスではなくキーボードやスイッチアクセスデバイスのみに依存しています。スクリーンリーダーを使用する全盲および弱視のユーザーもキーボードで操作しますが、支援技術がフォーカス位置を音声で知らせてくれる一方で、視力のあるキーボードユーザーはページ上で自分の位置を把握するために視覚的なフォーカスインジケーターのみに依存しています。

フォーカスされた要素が一部でも隠れていると、これらのユーザーは苛立たしく、場合によっては混乱を招く体験を強いられます。ページ上にフォーカスされた要素が存在しないように見えたり、文書のどこにいるのかを推測しなければならなかったりします。AAレベル(2.4.11)では、部分的な可視性は許容されます — コンポーネントの一部が見えていれば手がかりにはなります。AAAの達成基準はこの妥協を完全に排除し、部分的に隠れたフォーカスインジケーターであっても、コントラスト感度の低下、視野狭窄、画面の走査をより負担にする認知的な状態を持つユーザーには見逃されうることを認識しています。

具体的なシナリオを考えてみましょう。あるトルコのECサイトが、ビューポート上部に高さ80pxの固定ナビゲーションバー、下部に高さ60pxのスティッキーなクッキー同意バナーを使用しているとします。ユーザーがTabキーを押して商品カードを順に移動していくと、フォーカスされたカードの上端または下端 — フォーカスリングを含む — がこれらの固定された面の下に滑り込んでしまうことがあります。WCAG 2.4.11(AA)の下では、カードの一部でも見えていればサイトは合格です。2.4.12(AAA)の下では、カード全体が完全に見えていなければなりません。この違いには意味があります。ボタンラベルが一部隠れ、フォーカスリングも一部隠れていると、弱視のユーザーにとって、どの要素がアクティブなのか、またそれがどのようなアクションを実行するのかを実質的に判断できなくなる可能性があります。

世界保健機関によると、世界で約2.2億人が何らかの視覚障害を抱えており、運動障害はさらに数億人に影響を与えています。キーボードアクセシビリティの改善は、これらのグループだけでなく、速度のためにキーボードナビゲーションを好むパワーユーザー、ポインティングデバイスのない端末のユーザー、一時的に細かな運動制御が損なわれている状況のユーザーにも恩恵をもたらします。

障害者アクセスを超えて、完全に見えるフォーカスは一般的なユーザビリティを向上させ、サポートコストを削減します。障害の有無にかかわらず、すべてのユーザーがフォーカス位置を明確に追跡できると、フォームの完了率が向上し、エラー率が低下します。トルコ市場をターゲットとするサイトにとって、AAA準拠を示すことは成熟したアクセシビリティプログラムを示すものであり、ユーザーと機関の調達チームの双方からの信頼構築につながります。

関連するaxe-coreルール

WCAG 2.4.12は手動テストが必要なものとして分類されており、WCAG 2.2の追加項目です。この違反を確実に検出できる完全自動のaxe-coreルールは存在せず、その理由を理解することはテストパイプラインを構築するチームにとって重要です。

  • 手動検査 — focus-not-obscured-enhanced(自動ルールなし): axe-coreのような自動アクセシビリティスキャナーは、静的なDOMまたはレンダリング状態のスナップショットを対象に動作します。フォーカスされた要素が隠れているかどうかを検出するには、(1) すべてのインタラクティブ要素に対して順番にキーボードフォーカスをシミュレートし、(2) フォーカスによるスクロール後の要素の境界矩形を計算し、(3) すべてのfixedおよびsticky配置の要素とその境界矩形を特定し、(4) 幾何学的な重なりをテストする必要があります。理論的には部分的な自動化は可能ですが、スクロール挙動の動的な性質、CSSのscroll-padding、スムーススクロール、JavaScriptによるフォーカス管理により、実際には非常に信頼性が低くなります。あるビューポートサイズでは完全に見えているフォーカス要素が、別のサイズでは完全に隠れてしまうこともあります。axe-coreはこの達成基準を人間の判断が必要なものとして扱い、結果を自動違反ではなく「要レビュー」としてマークします。テスターはすべてのインタラクティブ要素をTabで手動でたどり、各関連ビューポート幅で完全に見えていることを目視で確認しなければなりません。
  • scrollable-region-focusable(axeルール): 2.4.12に直接対応するものではありませんが、このaxeルールは、スクロール可能領域内にあり、フォーカス可能でありながら正しくスクロールインされない可能性のある要素を検出します。これは、スティッキーヘッダーやフッターによってフォーカスが隠される原因となるスクロール管理の問題を示す関連シグナルであり、2.4.12の最も一般的な失敗パターンです。

自動ツールでは2.4.12の違反を確実に検出できないため、組織は手動のキーボードウォークスルーをQAプロセスに組み込む必要があります。理想的には複数のビューポートサイズで、ナビゲーションバー、チャットウィジェット、クッキーバナー、GDPR通知など、すべての永続的なUIレイヤーを有効にした状態で実施します。

テスト方法

  1. 自動ベースラインスキャン: axe DevToolsまたはLighthouseをページに対して実行し、scrollable-region-focusableの違反やCSSのoverflow問題など、関連する問題を特定します。これらの検出結果は直接の2.4.12違反ではありませんが、フォーカスが隠される問題を引き起こしやすいページ領域を示します。axe DevToolsでは、WCAG 2.2の達成基準でフィルタリングし、フォーカスの可視性に関連する「要レビュー」項目を確認します。
  2. すべての永続的なオーバーレイコンテンツの特定: キーボードテストの前に、ページ上のposition: fixedまたはposition: stickyを持つすべての要素 — 通常はナビゲーションバー、クッキーバナー、チャットウィジェット、フローティングアクションボタン、フッターツールバーなど — を目視で洗い出します。それらの高さと位置をメモし、ビューポートのどのゾーンを占有しているかを把握します。
  3. キーボードナビゲーションのウォークスルー: ページの先頭(または最初のモーダルを閉じた後)から開始し、Tabキーを繰り返し押してすべてのインタラクティブ要素にフォーカスを移動します。各フォーカス停止時に、フォーカスインジケーター(アウトラインやリング)を含むフォーカスされた要素全体が、遮られていないビューポート領域内に完全に収まっていることを確認します。部分的な可視性を許容してはいけません。要素またはそのフォーカスリングの一部でも固定要素の背後に消える場合は、2.4.12の失敗として記録します。
  4. 逆方向ナビゲーション: Shift+Tabを使って逆方向にナビゲーションしながら、同じウォークスルーを繰り返します。固定フッターは前方向だけのテストでは見落とされがちですが、逆方向にTab移動したときに要素を隠すことがあります。
  5. NVDA + Firefoxによるスクリーンリーダーテスト: NVDAを起動し、Firefoxを開いてTabキーでナビゲーションします。NVDAが要素へのフォーカスを読み上げたとき、その要素が完全に見えていることを目視で確認します。NVDAのフォーカスモードは、固定レイヤーを避けるように自動でスクロールするわけではないため、違反はブラウザネイティブの挙動とは異なる場合があります。
  6. VoiceOver + Safari(macOS/iOS)によるスクリーンリーダーテスト: VoiceOverを有効にし、Tabキー(またはiOSではスワイプ)でナビゲーションします。Safariのスクロール管理はChromiumとは異なる場合があり、Chromeでは見られなかったフォーカスの非表示状態が露呈することがあります。
  7. レスポンシブビューポートでのテスト: 一般的なブレークポイント — 幅320px、768px、1024px、1440px — でキーボードウォークスルーを繰り返します。スティッキー要素はブレークポイントによって高さが変わったり、表示されたり、完全に消えたりすることが多く、どのゾーンがリスクにさらされるかが変化します。
  8. ユーザー操作後のテスト: ドロップダウンメニューを開く、アコーディオンを展開する、モーダルを起動する、シングルページアプリケーションで新しいルートに移動するなどの操作を行います。各状態変化の後にTabナビゲーションを再開し、フォーカスの完全な可視性を再確認します。動的コンテンツは新たな固定オーバーレイを導入することが多いためです。

修正方法

スティッキーヘッダーがフォーカスされたリンクを隠す — 不適切な例

<!-- Fixed header with no scroll compensation -->
<header style='position:fixed; top:0; height:80px; background:#fff; width:100%;'>
  <nav>...</nav>
</header>

<main>
  <!-- When Tab reaches this link near the top of main, the header covers it -->
  <a href='/products'>View all products</a>
</main>

スティッキーヘッダーがフォーカスされたリンクを隠す — 適切な例

<!-- scroll-padding-top ensures focused elements scroll clear of the fixed header -->
<style>
  html {
    /* Match this value to the height of your fixed header */
    scroll-padding-top: 88px; /* 80px header + 8px breathing room */
  }
</style>

<header style='position:fixed; top:0; height:80px; background:#fff; width:100%;'>
  <nav>...</nav>
</header>

<main style='margin-top:80px;'>
  <!-- Focus now scrolls the element fully clear of the header -->
  <a href='/products'>View all products</a>
</main>

クッキーバナーがビューポート下部のインタラクティブ要素を覆う — 不適切な例

<!-- Cookie banner fixed to the bottom, no scroll compensation -->
<div id='cookie-banner' style='position:fixed; bottom:0; height:72px; width:100%; background:#222;'>
  <button>Accept All</button>
  <button>Manage Preferences</button>
</div>

<footer>
  <!-- These links at the bottom of the page get covered by the cookie banner -->
  <a href='/privacy'>Privacy Policy</a>
  <a href='/terms'>Terms of Service</a>
</footer>

クッキーバナーがビューポート下部のインタラクティブ要素を覆う — 適切な例

<!-- Add scroll-padding-bottom and body padding to compensate for the banner height -->
<style>
  html {
    scroll-padding-bottom: 80px; /* 72px banner + 8px breathing room */
  }
  body {
    padding-bottom: 80px; /* Prevent content from being permanently under the banner */
  }
</style>

<div id='cookie-banner' style='position:fixed; bottom:0; height:72px; width:100%; background:#222;'>
  <button>Accept All</button>
  <button>Manage Preferences</button>
</div>

<footer>
  <!-- Links now scroll fully into the unobscured viewport area -->
  <a href='/privacy'>Privacy Policy</a>
  <a href='/terms'>Terms of Service</a>
</footer>

JavaScriptのフォーカス管理が固定レイヤーを考慮していない — 不適切な例

<!-- SPA route change: focus moved to heading but scrollIntoView ignores header -->
<script>
function navigateTo(section) {
  const heading = document.querySelector('#' + section + ' h2');
  heading.setAttribute('tabindex', '-1');
  heading.focus();
  // scrollIntoView with no offset — heading scrolls behind fixed header
  heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>

JavaScriptのフォーカス管理が固定レイヤーを考慮していない — 適切な例

<!-- Use scroll-margin-top on the target element, or manually offset scrollY -->
<style>
  .focus-target {
    /* scroll-margin-top offsets this element's scroll position from the top */
    scroll-margin-top: 96px;
  }
</style>

<script>
function navigateTo(section) {
  const heading = document.querySelector('#' + section + ' h2');
  heading.setAttribute('tabindex', '-1');
  // scroll-margin-top on the element handles the visual offset automatically
  heading.classList.add('focus-target');
  heading.focus();
  // scrollIntoView now respects scroll-margin-top, clearing the fixed header
  heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>

よくある間違い

  • bodyではなくhtmlscroll-padding-topを設定してしまう: CSSのscroll-paddingプロパティはスクロールコンテナに適用する必要があります。ページ全体のスクロールでは、スクロールコンテナはbodyではなくhtml要素です。bodyに適用しても、ほとんどのブラウザでは効果がなく、最も一般的な実装ミスの1つです。
  • すべてのブレークポイントで実際のヘッダーの高さと一致しない固定ピクセル値をscroll-padding-topにハードコードする: ヘッダーがモバイルで小さな高さに折りたたまれたり、デスクトップでセカンダリナビゲーションバーを含むように拡張されたりすると、静的なオフセット値が不正確になります。JavaScriptで更新されるCSSカスタムプロパティを使用するか、相対単位とcalc()を組み合わせて値を同期させてください。
  • ページ内アンカーのターゲットにscroll-margin-topを設定し忘れる: Tabナビゲーションに対してグローバルなscroll-padding-topが正しく設定されていても、プログラム的にフォーカスを受け取るアンカーリンクのターゲット(例:スキップリンク、SPAのハッシュナビゲーション)は、特定の要素にscroll-margin-topが設定されていない限り、ヘッダーの下に隠れてしまうことがあります。
  • クッキーバナーを閉じた後に再テストしない: 多くのチームは、クッキーバナーを受け入れた後にのみキーボードナビゲーションをテストします。バナーはビューポート下部を占有するため、下部に固定されたフォーカス可能要素は、バナーが表示されている間だけ隠れる場合があります。すべての永続的なUIレイヤーが完全に表示された状態で必ずテストしてください。
  • 1つのビューポート幅でしかテストしない: スティッキー要素は、ブレークポイントによって高さが変わったり、表示されたり、完全に消えたりすることが多いです。375pxで発生する失敗が1440pxでは発生しないことも、その逆もあります。1つのサイズだけでテストすると、現実世界の違反のかなりの割合を見逃します。
  • 親コンテナにoverflow: hiddenを使用してフォーカスインジケーターをクリップしてしまう: カードコンポーネントやコンテナにoverflow: hiddenが設定されていると、子要素のブラウザデフォルトのフォーカスアウトラインはコンテナの境界でクリップされます。これにより、DevToolsの要素検査ではフォーカスが完全に見えているように見えても、ユーザーには視覚的に切れて見えることがあります。
  • スクリーンリーダーが自動でスクロールを処理するので視覚的テストは不要だと考える: スクリーンリーダーはフォーカスされた要素を音声で読み上げますが、スクリーン拡大ツールを使用するユーザーを含む視力のあるキーボードユーザーは、視覚的な位置に完全に依存しています。視覚的に隠れたフォーカス状態は、スクリーンリーダーの挙動にかかわらず、実際の失敗です。
  • モーダルダイアログやドロワーオーバーレイをテストしない: モーダルが開き、フォーカスがその内部に移動したとき、背景やモーダルの枠自体が、ダイアログ内で最初にフォーカスされる要素を隠してしまうことがあります。これは特に、側面や下部からアニメーションで表示されるドロワー型パネルでよく見られます。
  • ライブチャットバブルやインタースティシャル広告バナーなどのサードパーティウィジェットを無視する: IntercomやZendeskなどのフローティングチャットウィジェットや、タグマネージャーによって挿入される固定プロモーションバナーは、作成者が用意したコンテンツであり、この達成基準の対象範囲に含まれます。これらはメインのコードベースの外で管理されているため、チームに見落とされがちです。
  • 自動アクセシビリティスキャンだけに頼り、チケットをクローズしてしまう: 2.4.12は手動テストが必要なため、axe-coreのスキャン結果がクリーンであっても準拠を保証するものではありません。自動結果だけに基づいてアクセシビリティチケットをクローズすると、この達成基準は一貫して見逃されます。

トルコのアクセシビリティ規制との関係

トルコの大統領通達 2025/10は、2025年6月21日付官報第32933号で公布され、トルコで事業を行う幅広い主体に対して、ウェブおよびモバイルのアクセシビリティ要件を法的に定めています。この通達は、WCAG 2.1レベルAAをベースラインの準拠標準として採用しており、WCAG 2.4.12はWCAG 2.2のAAA達成基準であるため、現行の規制の下では直接義務付けられてはいません。しかし、トルコのアクセシビリティ枠組みにおけるその位置付けは、いくつかの理由から重要です。

大統領通達2025/10の対象となる主体には、あらゆるレベルの公的機関・政府機関、ECプラットフォーム、銀行および金融サービス提供者、病院および医療機関、20万以上の加入者を持つ通信会社、旅行代理店、民間輸送会社、国民教育省(MoNE)から認可を受けた私立学校が含まれます。これらすべての組織にとって、WCAG 2.1 AAへの準拠は法的義務であり、通達は関係監督機関による監査メカニズムを通じて執行されることが見込まれています。

AAA準拠は通達によって要求されてはいませんが、規制対象セクターの組織には、WCAG 2.4.12への準拠を追求する強い実務的理由があります。第一に、トルコの規制環境は進化しつつあります。この通達は、従来のガイダンスと比べてアクセシビリティの執行を大きく強化するものであり、将来の改訂でWCAG 2.2の採用や準拠レベルの引き上げが行われる可能性があります。今のうちからAAAレベルの実務を構築しておくことで、規制変更への備えが整います。第二に、公的調達プロセスやEU市場へのアクセスでは、強化されたアクセシビリティプログラムを示せるサプライヤーがますます優遇されており、AAA準拠の文書は競争上の差別化要因となります。

第三に、そしてWCAG 2.4.12に最も直接関係する点として、この達成基準は、トルコにおける支援技術ユーザー — 運動、視覚、認知の障害を合わせると数百万人と推計される人口 — に不均衡な影響を与える失敗パターンに対処するものです。固定ナビゲーションクロームや永続的な通知レイヤーに大きく依存している銀行、病院、電子政府ポータルは、まさにフォーカスが隠れる失敗が最も頻発するサイトです。WCAG 2.4.12への完全準拠に投資することは、すべてのユーザーにサービスを提供する真摯な姿勢を示すものであり、通達の文言がまだ要求していない部分においてもその精神に合致し、トルコでの執行が成熟するにつれて法的・評判上のリスクを軽減します。

Accsible overlay SDKを利用している組織に対しては、このプラットフォームがキーボードフォーカスの経路を監査し、スティッキー配置の競合を特定するツールを提供しており、大統領通達2025/10の必須AA要件と、WCAG 2.4.12のような任意のAAA拡張の両方を支援します。