WCAG 達成基準 · Level A

WCAG 3.2.1: フォーカス時

WCAG 3.2.1 On Focus は、任意のユーザーインターフェイスコンポーネントがキーボードフォーカスを受け取ったとき、予期しないコンテキストの変化を引き起こしてはならないと求めています。これは、ページの効果的なナビゲーションを不可能にしてしまうような、混乱を招く予測不能な挙動から、キーボードおよび支援技術の利用者を保護するためのものです。

このルールが意味すること

WCAG 達成基準 3.2.1「フォーカス時」(レベル A)は、「いずれかのコンポーネントがフォーカスを受け取っても、コンテキストの変化を開始しない。」と定めています。平たく言えば、TabShift+Tab、矢印キー、その他のキーボード操作によってフォーカスをインタラクティブ要素に移動させる行為だけで、ページ上で劇的かつ予期しないことが起きてはならない、という意味です。

コンテキストの変化とは、WCAG によれば、ユーザーが気づかないうちに行われるとユーザーを混乱させうる、ページ内容の大きな変化と定義されています。仕様では、コンテキストの変化として 4 つの具体的なタイプを挙げています。ユーザーエージェントの変化(新しいブラウザウィンドウやタブを開くなど)、ビューポートの変化(ページの離れた位置へ自動スクロールするなど)、フォーカス自体の変化(自動的にフォーカスを別の場所へ移動させるなど)、そしてページの意味を大きく変えるコンテンツの変化(フォーム送信や全く別のビューの読み込みなど)です。

この達成基準が強調している重要な違いは、フォーカスを受け取ることと、コントロールをアクティブ化することの区別です。ボタンに Tab でフォーカスしただけでフォームが送信されるのは違反です。しかし、そのボタンにフォーカスがある状態で EnterSpace を押すことは、意図的で明示的なアクティブ化であり、完全に許容され、むしろ期待される挙動です。ユーザーの意図こそが、予測可能なインタラクションと混乱を招くインタラクションを分けるものです。

この達成基準に違反する一般的なパターンには次のようなものがあります。

  • ユーザーが選択を確定する前ではなく、ある選択肢がフォーカスを受け取った瞬間に新しい URL へ自動的に移動してしまう <select> ドロップダウン。
  • いずれかの入力フィールドがフォーカスを受け取った瞬間に、ユーザーのアクティブ化なしでモーダルダイアログを開く日付ピッカーウィジェット。
  • ナビゲーションドットがフォーカスを受け取ると、自動的に次のスライドへ進んでしまうカルーセルやスライドショー。
  • フォーカスによってトリガーされた際に、警告なしにキーボードフォーカスを自分自身へ同時に移動させ、ユーザーを予期しない位置に取り残してしまうツールチップやポップオーバー。
  • フォーカスを受け取った瞬間に即座に送信され、ページを再読み込みしてしまう検索フィールド。

明示的に違反ではないパターンには、フォーカスを移動させたりページの主要コンテンツを変えたりせず、視覚的にのみ表示されるツールチップや説明パネル、フォーカスされた要素の周囲に表示されるアウトラインやリングなどのフォーカスインジケーター、そしてユーザーが置いたフォーカスがそのまま維持される限りにおいて、インラインで追加コンテンツを表示するために要素が展開するケースなどが含まれます。

WCAG 3.2.1 には公式な例外は定義されていません。この達成基準はページ上のすべての UI コンポーネントに普遍的に適用されます。ただし WCAG の Understanding 文書では、ユーザーの意図的なアクティブ化(クリック、Enter、Space)によって引き起こされるコンテキストの変化は、この達成基準の対象外であると述べています。この基準が扱うのは、あくまで受動的な「フォーカスを受け取る」行為だけだからです。

なぜ重要か

「フォーカス時」達成基準は、原則 3「理解可能」の中に位置づけられています。予測可能性はユーザビリティの根本的な前提条件だからです。フォーカスだけに反応してページが予期せぬ挙動をすると、ユーザーのニーズや利用しているツールによって、その影響は軽い混乱から完全なアクセス不能にまで及びます。

キーボードのみのユーザー(運動障害、反復性ストレス障害、麻痺などによりマウスを使えない人)は、キーボードナビゲーションだけに依存しています。フォームフィールドに Tab で入っただけでページが再読み込みされると、それまで入力したデータをすべて失い、目的から遠ざかる場所へリダイレクトされてしまうかもしれません。このような中断から立ち直るには多大な時間と労力がかかることがあり、場合によってはそのまま利用を諦めてしまうこともあります。

スクリーンリーダーユーザー(多くの場合キーボードのみのユーザーでもあります)は、さらに別のレベルの混乱に直面します。スクリーンリーダーは、現在フォーカスされている要素をユーザーに読み上げます。もしフォーカスが予期せず新しい要素へ飛んでしまうと(たとえば自動的に開いたモーダルなど)、スクリーンリーダーは新しいコンテキストを読み上げますが、ユーザーには何が起きたのか、なぜそうなったのかの手がかりがありません。これは、何の前触れもなく物理的に別の部屋へ連れて行かれるのに近い状況です。

認知障害のあるユーザー(ADHD、不安障害、記憶障害などを含む)は、ページのメンタルモデルを構築・維持するために予測可能なインターフェースに依存しています。突然の、説明のないコンテキストの変化はそのモデルを打ち砕き、混乱や不安、エラーを生み出します。WebAIM Million プロジェクトの調査では、予期しない挙動を伴う複雑なインタラクティブコンポーネントが、認知障害のあるユーザーからのアクセシビリティに関する苦情の主要な原因の一つであることが一貫して示されています。

ロービジョンのユーザーで、ZoomText や Windows Magnifier のような画面拡大ソフトを使っている人は、一度に画面のごく一部しか見ることができません。フォーカスによって自動スクロールやナビゲーションが起きると、関連するコンテンツが拡大されたビューポートの外へ完全に移動してしまい、画面の空白や無関係な領域だけを見せられることになります。

現実の具体的なシナリオを考えてみましょう。あるトルコの銀行のオンライン送金フォームには、送金先銀行を選択するためのドロップダウンメニューがあります。開発者は <select> 要素に onchange 形式のイベントを実装していますが、それは選択の確定時ではなく、矢印キーでフォーカスが任意の選択肢に移った瞬間に発火するようになっています。スクリーンリーダーユーザーがそのフィールドに Tab で入り、利用可能な銀行を確認しようと Down キーを押した途端、フォーム送信やページ再読み込みが即座に起きてしまいます。ユーザーは送金を完了できず、何が問題だったのかも把握できません。このシナリオは仮定ではなく、初期の多くのシングルページアプリケーションで実際に見られたパターンです。

アクセシビリティを超えて、ユーザビリティやビジネス面でも具体的なメリットがあります。フォーカスを乗っ取らないフォームは離脱率が低くなります。予測可能に振る舞うページは、あらゆるユーザー層を対象としたユーザビリティテストで高い評価を得ます。検索エンジンクローラーも、予測可能なナビゲーションフローから恩恵を受けます。フォーカスイベントによって引き起こされる予期しないリダイレクトは、特定の動的レンダリング環境ではクロールロジックを混乱させる可能性があるからです。

関連する Axe-core のルール

WCAG 3.2.1「フォーカス時」は、ユーザーの意図を自動ツールが確実に判断したり、起こりうるすべてのコンテキスト変化を予測したりすることができないため、手動テストが必要です。axe-core や類似の自動スキャナーは静的な HTML や ARIA 属性を解析できますが、フォーカスイベントに応じた実行時の JavaScript の挙動、特にその挙動が WCAG の定義する「重大な」コンテキストの変化に該当するかどうかを観察することはできません。focus イベントでフォーカスを移動したり、フォームを送信したり、URL へナビゲートしたりするスクリプトは、ツールが実際にすべてのインタラクティブ要素に対してフォーカスインタラクションをシミュレートし、その後 DOM、ビューポート、URL に何が変化したかを分析しない限り、静的スキャナーからは見えません。このレベルの挙動シミュレーションを、自動処理の 1 回のパスで信頼性高く行うことは、許容できないほど高い誤検出率なしには実現できません。

  • 手動テスト必須 — フォーカス時のコンテキスト変化: テスターはページ上のすべてのインタラクティブ要素(リンク、ボタン、入力欄、セレクト、カスタムウィジェット)を Tab で順にたどり、フォーカスだけで — アクティブ化なしに — WCAG が定義するコンテキストの変化が起きないかどうかを観察する必要があります。これには、URL の変化、新しいウィンドウやタブのオープン、フォーカスが現在の要素から離れること、フォーム送信、大きなコンテンツの置き換えなどを監視することが含まれます。自動ツールは、focusfocusinonfocus といったフォーカス関連イベントに紐づいた JavaScript イベントリスナーを、手動レビューの候補としてフラグ付けすることはできますが、それらのハンドラーが違反となるコンテキスト変化を引き起こすかどうかを判断することはできません。

テスト方法

  1. 自動プレスキャン: 対象ページに対して axe DevTools(ブラウザ拡張または CLI)や Google Lighthouse を実行します。どちらのツールも「フォーカス時」の違反を決定的に検出することはできませんが、axe DevTools はフォーカス管理に関連する問題(scrollable-region-focusable やフォーカストラップのパターンなど)を表面化させることがあり、より綿密な手動検査が必要な箇所を示してくれます。axe DevTools の「要レビュー(Needs Review)」パネルを活用してください。そこにフラグされた項目は、多くの場合、人間の判断を要するインタラクティブコンポーネントの挙動に関係しています。
  2. すべてのインタラクティブ要素の特定: キーボードテストの前に、すべてのインタラクティブコンポーネントのリストを作成します。リンク、ボタン、フォーム入力、ドロップダウン、チェックボックス、ラジオボタン、日付ピッカー、カルーセル、アコーディオン、タブ、モーダル、そして tabindex を使用しているカスタムウィジェットなどです。特に、focusfocusin イベントを監視しているカスタム JavaScript ウィジェットに注意を払ってください。
  3. キーボードのみのナビゲーションテスト: マウスを使わず、キーボードだけを用いて、ページ上のすべてのフォーカス可能要素を Tab キーで順に移動します。各 Tab キー押下の後、他のキーを押す前に観察します。URL は変わったか? 新しいウィンドウやタブは開いたか? フォーカスは今 Tab で移動した要素から離れたか? フォームは送信されたか? ページの主要コンテンツは劇的に変化したか? いずれかに「はい」と答えられる場合、その箇所は違反の候補です。
  4. select 要素のテスト: 任意の <select> ドロップダウンにフォーカスを当てます。Enter や Space を押さずに、上下矢印キーを使って選択肢を移動します。選択肢の移動によって、ナビゲーション、フォーム送信、コンテキストの変化が起きないことを確認します。これは最も違反が多いパターンの一つです。
  5. NVDA + Firefox: NVDA(無料、Windows)を有効にします。Firefox を開き、対象ページへ移動します。Tab キーでインタラクティブ要素を順に移動します。NVDA の読み上げに耳を傾け、Enter や Space を押していないのに、Tab を押した後で全く別のページ部分や新しいページコンテキストが読み上げられ始めた場合、それは違反の強い兆候です。
  6. JAWS + Chrome: JAWS を有効にします。Chrome を開きます。Tab キーでナビゲートします。JAWS はフォーカスされた各要素を読み上げます。自分が意図的に移動していないのに、新しいダイアログやページ、フォーカス位置が突然読み上げられるかどうかを確認します。
  7. VoiceOver + Safari(macOS/iOS): VoiceOver を有効にします(macOS では Cmd+F5)。Tab(または iOS ではスワイプ)でナビゲートします。予期しないコンテキストの変化がないかを監視します。iOS では、重度の運動障害のあるユーザーを想定してスイッチコントロールによるスキャン操作でもテストしてください。
  8. ブラウザ DevTools によるイベントリスナーの検査: Chrome DevTools で、疑わしいインタラクティブ要素を選択し、Elements パネルの「Event Listeners」を開きます。focusfocusin のリスナーを探します。存在する場合、その JavaScript を確認し、ハンドラーがナビゲーション、フォーム送信、フォーカス移動、その他コンテキストを変える動作を引き起こしていないかを判断します。

修正方法

自動送信される select ドロップダウン — 不適切な例

<!-- FAIL: 矢印キーで選択肢を選ぶと即座に新しい URL へ移動してしまう -->
<label for='region'>Select Region</label>
<select id='region' onchange='window.location = this.value;'>
  <option value='/istanbul'>Istanbul</option>
  <option value='/ankara'>Ankara</option>
  <option value='/izmir'>Izmir</option>
</select>

自動送信される select ドロップダウン — 適切な例

<!-- PASS: ユーザーが Go ボタンを明示的にアクティブ化したときだけナビゲーションが行われる -->
<label for='region'>Select Region</label>
<select id='region'>
  <option value='/istanbul'>Istanbul</option>
  <option value='/ankara'>Ankara</option>
  <option value='/izmir'>Izmir</option>
</select>
<button type='button' onclick='navigateToRegion()'>Go</button>

<script>
function navigateToRegion() {
  var select = document.getElementById('region');
  window.location = select.value; // 意図的なボタンのアクティブ化時にのみ実行される
}
</script>

入力フォーカスでモーダルが開く — 不適切な例

<!-- FAIL: 日付入力にフォーカスした瞬間にモーダルダイアログが開き、フォーカスが移動してしまう -->
<label for='departure'>Departure Date</label>
<input type='text' id='departure' onfocus='openDatePickerModal()' />

<script>
function openDatePickerModal() {
  var modal = document.getElementById('date-modal');
  modal.style.display = 'block';
  modal.querySelector('button').focus(); // ユーザーの意図なしにフォーカスを移動させてしまう
}
</script>

入力フォーカスでモーダルが開く — 適切な例

<!-- PASS: 日付ピッカーはユーザーが明示的にクリックまたは Enter/Space を押したときだけ開く -->
<label for='departure'>Departure Date</label>
<input type='text' id='departure' readonly aria-haspopup='dialog'
       aria-label='Departure Date — press Enter to open date picker' />
<button type='button' id='open-picker'
        aria-controls='date-modal'
        onclick='openDatePickerModal()'>
  Choose Date
</button>

<script>
function openDatePickerModal() {
  // 明示的なアクティブ化(ボタンのクリックまたは Enter/Space)時にのみ呼び出される
  var modal = document.getElementById('date-modal');
  modal.removeAttribute('hidden');
  modal.querySelector('[data-initial-focus]').focus();
}
</script>

フォーカスで自動的に進むカルーセル — 不適切な例

<!-- FAIL: ナビゲーションドットにフォーカスするとカルーセルのスライドが進み、ページコンテンツが変化してしまう -->
<div class='carousel-dots'>
  <button class='dot' onfocus='showSlide(0)'>1</button>
  <button class='dot' onfocus='showSlide(1)'>2</button>
  <button class='dot' onfocus='showSlide(2)'>3</button>
</div>

フォーカスで自動的に進むカルーセル — 適切な例

<!-- PASS: ドットが明示的にアクティブ化されたとき(クリック/Enter)だけカルーセルのスライドが変わる -->
<div class='carousel-dots' role='tablist' aria-label='Carousel navigation'>
  <button class='dot' role='tab' aria-selected='true'
          aria-controls='slide-0' onclick='showSlide(0)'>
    Slide 1
  </button>
  <button class='dot' role='tab' aria-selected='false'
          aria-controls='slide-1' onclick='showSlide(1)'>
    Slide 2
  </button>
  <button class='dot' role='tab' aria-selected='false'
          aria-controls='slide-2' onclick='showSlide(2)'>
    Slide 3
  </button>
</div>
<!-- onclick は Tab フォーカスではなく、意図的なアクティブ化時にのみ発火する -->

よくある間違い

  • ナビゲーション要素で onclick の代わりに onfocus を使うこと: 開発者がナビゲーションリンクやボタンに onfocus ハンドラーを付けて遷移先を「事前読み込み」しようとし、意図せずプリフェッチではなく完全なナビゲーションを引き起こしてしまうことがあります。コンテキストを変えるあらゆるアクションには、必ず onclick か、Enter/Space を判定する onkeydown を使用してください。
  • 送信アクションなしで <select> 要素に onchange をバインドすること: デスクトップブラウザでは、<select>onchange は選択肢が確定したときに発火しますが、古い実装や一部のモバイルブラウザでは、矢印キーで選択肢を移動しただけで発火することがあります。select によるナビゲーションは、必ず明示的な送信ボタンと組み合わせるか、<button type='submit'> を持つ <form> を使用してください。
  • focus イベントハンドラー内でプログラム的にフォーカスを移動すること: ある要素の onfocusfocusin ハンドラーの中で element.focus() を呼び出すと、予期しないフォーカスジャンプが発生します。これは直接的な違反です。ユーザーは要素 A に Tab で移動したのに、フォーカスが何の説明もなく要素 B に移ってしまうからです。フォーカスの移動は、常にユーザーの意図的なアクションにのみ応じて行ってください。
  • トリガー要素のフォーカスイベントでモーダルダイアログを開くこと: よくある手抜きとして、トリガーボタンや入力フィールドの focus イベントにモーダルを開くハンドラーを紐づけるケースがあります。モーダルはクリック、Enter キー、Space キーといったアクティブ化にのみ応じて開くべきであり、フォーカスだけで開いてはなりません。
  • フォーカス時にビューポートコンテキストを変えるメディアやアニメーションを自動再生すること: 再生ボタンがフォーカスを受け取った瞬間にフルスクリーン動画や大きなアニメーションを開始するヒーローバナーは、視覚的コンテキストを大きく変えてしまいます。再生アクションはフォーカスイベントではなくアクティブ化イベントに紐づけてください。
  • フォーカス時に新しいコンテンツへスクロールするライブリージョン更新をトリガーすること: 一部の動的ウィジェットは、入力がフォーカスを受け取った瞬間にライブリージョンを更新し、その領域へビューポートをスクロールさせます。これはビューポートコンテキストを変え、画面拡大ユーザーを混乱させます。可能な限り、ライブリージョンの更新をフォーカスイベントから切り離してください。
  • ユーザーへの説明なしに、即座にユーザーを閉じ込めるカスタムフォーカストラップを実装すること: あるカスタムコンポーネントがフォーカスを受け取った瞬間から、ユーザーがフォーカストラップの中にいることを告げることなく、すべての Tab キー押下をインターセプトする場合、この達成基準の文言と精神の両方に反します。フォーカストラップは完全に開いたモーダルダイアログの内部でのみ適切であり、ユーザーには退出方法を知らせなければなりません。
  • CSS の :focus を使って、フォーカス可能な子要素を含むドロップダウンメニューに display: block を適用し、その結果として予期しないフォーカス移動の連鎖を引き起こすこと: CSS のみでフォーカス駆動のメニューを表示すると、ブラウザのフォーカス順序が新たに表示された要素へ移動する際に、混乱を招くジャンプが発生することがあります。表示されるメニューが予期されたものであることを確認し、aria-expanded のような ARIA 属性で明確に伝えてください。
  • フォーカス挙動を監査せずにサードパーティのウィジェットライブラリに依存すること: 多くの UI コンポーネントライブラリ(日付ピッカー、リッチテキストエディタ、select2 風ドロップダウンなど)は、歴史的に focus イベントでポップアップを開いたりフォーカスを移動させたりすることで 3.2.1 に違反してきました。ライブラリ側がアクセシビリティ対応を謳っているかどうかにかかわらず、サードパーティコンポーネントは導入前に必ず手動で監査してください。
  • シングルページアプリケーション(SPA)のルーティングコンテキストでのテストを怠ること: React、Vue、Angular などの SPA では、ナビゲーションリンクのフォーカスイベントが、ルーターのプリフェッチロジックやイベントバブリングを通じてルート変更を引き起こすことがあります。特にフォーカスイベントが適切に伝播停止されていない場合に起こりがちです。SPA のナビゲーションコンポーネントは、3.2.1 への準拠について明示的にテストしてください。

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

トルコの大統領通達 2025/10 は、2025 年 6 月 21 日付官報第 32933 号で公布され、技術標準として WCAG 2.2 を明示的に参照する強制的なウェブアクセシビリティ要件を定めています。WCAG 3.2.1「フォーカス時」はレベル A の達成基準であり、この通達の下では必須遵守のベースラインに位置づけられます。レベル A の達成基準には例外はなく、対象となるすべての主体が定められた期限内にこれらを満たさなければなりません。

公的機関は、通達の公布日から1 年以内に完全な適合を達成することが求められます。対象となる民間事業者には2 年が与えられます。大統領通達 2025/10 の対象主体には、幅広い組織が含まれます。すべての公的機関・行政機関、e コマースプラットフォームおよびオンラインマーケットプレイス、銀行および金融機関、病院および民間医療提供者、20 万人以上の加入者を持つ通信事業者、旅行代理店および予約プラットフォーム、民間輸送会社、そして国民教育省(MoNE)に認可された私立学校および教育機関です。

WCAG 3.2.1「フォーカス時」がこれらの主体にとって持つ意義は、直接的かつ実務的です。たとえば、商品カテゴリのドロップダウンがフォーカス時に自動ナビゲーションする e コマースプラットフォームでは、モビリティに障害のあるキーボードユーザーは商品カテゴリを閲覧できず、購入を諦めてしまうでしょう。フォーカスによって送信がトリガーされる銀行のオンライン送金フォームは、スクリーンリーダーユーザーにとって意図しない金融取引や繰り返し失敗する試行を引き起こす可能性があります。日付フィールドがフォーカス時にモーダルを開く病院の予約システムは、障害のある患者が自力で診療予約を行うことを妨げるかもしれません。

この通達の下では、遵守しない場合、対象主体は行政制裁や評判リスクにさらされます。現在デジタルトランスフォーメーションを進めている、あるいは新たなウェブシステムを調達している主体にとっては、苦情が出てから後付けで対応するのではなく、今の段階で調達要件や開発者ガイドラインに WCAG 3.2.1 への準拠を組み込む方が、コスト効率が高く、規制の趣旨にもより合致します。Accsible overlay SDK を利用している組織は、フォーカスイベント時の予期しない挙動を特定・是正するための組み込みのフォーカス管理ツールを活用でき、より広範な WCAG 2.2 レベル A 準拠ワークフローの一部として恩恵を受けることができます。