トピックを検索
キーボードで閉じられないポップアップ
会員登録を勧めるポップアップがTabキーを内側に留めたままEscapeキーに反応せず、×もspanで描かれているため、キーボードでは登録しないかぎり閉じられない
<span class="popup-close">×</span> 問題
キーボードの利用者が会員登録のポップアップを閉じられず、登録しないかぎり背後のページを見られない
陶器の店Juniper Ceramicsのサイトでは、ページを開いて5秒たつと、ポップアップが現れます。「初回のご注文が10%オフ」という見出し、メールアドレスの欄、「登録する」ボタン、右上の×です。マウスの利用者は×をクリックして、そのまま買い物を続けます。キーボードの利用者の場合、フォーカスはすでにメールアドレスの欄にあります。Tabキーで「登録する」へ移り、次のTabで欄に戻ります。Shift + Tabでも、逆向きに同じ二つを回るだけです。×には一度も届かず、Escapeキーも効きません。ポップアップからフォーカスを出せるのは、メールアドレスを入力し、Enterキーでフォームを送信したときだけです。アドレスを渡したくない人は、背後のページへ進めません。
達成基準2.1.2は、キーボードでフォーカスを移せる部分からは、キーボードだけでフォーカスを外へ移せることを求めています。通常のキー以外が必要な場合は、その方法をページで知らせる必要があります。ダイアログで通常使うキーはEscapeキーです。解説書(Understanding)はEscapeキーをよく使われる標準的な抜け出し方として挙げ、ARIA Authoring Practicesもすべてのモーダルダイアログで、Escapeキーに閉じる操作を割り当てています。
ポップアップのスクリプトはTabキーを内側に留めています。モーダルとしては正しい動作で、オーバーレイの背後のページへフォーカスが迷い出るのを防いでいます。ただ、フォーカスを留めるなら抜け出す方法も必要なのに、このポップアップにはキーボードで使える出口がありません。×はクリック処理を付けた span で、span はフォーカスを受け取らないため、Tabキーは×に止まらずに入力欄と「登録する」を回ります。Escapeキーを受け取る処理もありません。残る出口は、登録することだけです。
2.1.2は、ページ上のすべての内容に適用される四つの達成基準の一つで、サイトが適合の根拠にしていない部分も対象になります(WCAGの適合要件にある「非干渉」)。マーケティング用のスクリプトが追加したポップアップも、コードの出どころにかかわらず、ページの一部です。
マウスなら1回のクリックで閉じられるため、ポップアップの確認もたいていそれで済まされます。×はずっと画面に出ているので、画面を見ながらキーボードで操作する人には、出口が見えているのに届きません。
10グループ中2グループに影響
いくつかのコントロールを回り続けるTabキーは、ほとんどの場所ではキーボードトラップですが、開いたモーダルの中では本来の動作です。そのため、Tabキーでページを移動するだけのチェックでは、見逃すしかありません。ダイアログに出口があるかどうかは、Escapeキーを押し、閉じるボタンを調べて初めて分かります。また、多くのポップアップは、ページを開いて数秒後や、ポインターがアドレスバーへ向かったときに開くため、手早い自動チェックが終わった後に現れます。マーケティングツールの管理画面で設定され、サイト自身のコードと一緒に確認されないことも少なくありません。ポップアップの実装をAIアシスタントに頼むと、このパターンを書くことがあります。フォーカスを留める処理があるため、丁寧に作られたように見えるからです。
修正方法
×を名前のあるボタンにし、showModal() かキー操作の処理で、Escapeキーでポップアップを閉じる
- 1フォーカスを留めるポップアップは、Escapeキーで閉じる
- 2閉じるボタンは名前のあるボタンにし、Tabキーで届くようにする
- 3閉じたら、利用者がいた場所へフォーカスを戻す
<div class="popup-overlay" hidden> <div class="popup" role="dialog" aria-modal="true" aria-labelledby="popup-title"> <span class="popup-close">×</span> <h2 id="popup-title">初回のご注文が10%オフ</h2> <p>新作やセールのお知らせをお届けします。</p> <form class="popup-form"> <label for="popup-email">メールアドレス</label> <input id="popup-email" name="email" type="email" autocomplete="email" required> <button>登録する</button> </form> </div></div>HTML標準のdialog要素
<dialog class="popup" aria-labelledby="popup-title"> <button type="button" class="popup-close" aria-label="閉じる">×</button> <h2 id="popup-title">初回のご注文が10%オフ</h2> <p>新作やセールのお知らせをお届けします。</p> <form class="popup-form"> <label for="popup-email">メールアドレス</label> <input id="popup-email" name="email" type="email" autocomplete="email" required> <button>登録する</button> </form></dialog>この方法を選ぶ理由:まずHTMLの要素を使う
フォーカスを留める処理は、「登録する」からのTabを入力欄へ戻します。span はこの処理が集める一覧にも、Tabキーの順序にも入っていないため、×はポインターでしか使えません。Escapeキーを扱う処理はなく、ポップアップを閉じるコードは、×のクリックのほかにはフォームの送信だけです。
showModal() は背後のページを操作できない状態にするため、Tabキーが背後のページに届くことはありません。ただ、ブラウザーはそのままだと、ダイアログの最後のコントロールからのTabを、アドレスバーなどブラウザー自身のコントロールへ出してしまいます。そこで短い処理を加えて×へ戻し、ARIA Authoring Practicesのモーダルダイアログのパターンと同じように、フォーカスをポップアップの中に留めます。Escapeキーで閉じ、閉じるとフォーカスは、ポップアップが開いたときにフォーカスのあった要素へ戻ります。×は button になったので、Tabキーで届き、EnterキーとSpaceキーで押せます。aria-label で付けた「閉じる」という名前が、記号の代わりにスクリーンリーダーで読まれます。背景を暗くする表示はブラウザーが ::backdrop 擬似要素で描くため、オーバーレイの div は不要になります。
どちらも、キーボードに二つの出口(Escapeキーと、Tabキーで届く×)を用意し、閉じた後にフォーカスを戻します。HTML標準の dialog を選んでください。ブラウザーがEscapeキーで閉じ、背後のページを操作できない状態にするため、コードが少なく、後からスクリプトを変えてもEscapeキーという出口が失われません。独自のポップアップを残すのは、そのマークアップがコンポーネントやツールから出力されていて、dialog 要素に置き換えられない場合です。その場合は、変更のたびにEscapeキーと×を確認し直してください。
会員登録のポップアップ、離脱時のクーポン、Cookieの同意ダイアログは、マーケティングツールや同意管理ツールで設定されることが多く、コードは提供元のものです。できることは次のとおりです。
- 公開する前に、ツールが出すポップアップを一つずつ「修正の確認」の手順で試す。ツールが許せば、テスト用のページで行います。
- ツールの設定を確認する。アクセシブルなテンプレートや「Escapeキーで閉じる」設定があるものもあります。閉じるボタンが見えるものを選び、ページごとに開き直すポップアップは無効にします。
- ポップアップがEscapeキーで閉じるか、閉じるボタンが名前のあるボタンか、閉じた後にフォーカスがどこへ移るかを、提供元に尋ねる。
- うまくいかない点を、再現手順を添えて提供元に報告する。直るまでは、アクセシビリティ方針に既知の問題として記載し、キーボードで閉じられないポップアップは止めます。どのような経路でページに入ったものでも、2.1.2の対象です。
提供元のスクリプトは、自社のリリースと関係なく変わります。提供元の更新やポップアップの設定の変更のたびに、確認を繰り返してください。
Cookieの同意ダイアログ。 ページの操作を止めるCookieの同意ダイアログにも、同じ原則が当てはまります。「すべて許可」「すべて拒否」「Cookieを選ぶ」のどの選択肢も、Tabキーで届く button にし、Escapeキーでは何も保存せずに閉じて、次の訪問でもう一度尋ねます。バナーをページのどこに置くか、フォーカスを覆わないかは、フォーカスを隠し、最後にしか届かないCookieバナーで扱っています。
Escapeキーを打ち消さない。 HTML標準のダイアログは、Escapeキーで閉じる前に cancel イベントを発生させます。選択を強制するために、そこで preventDefault() を呼ぶと、閉じるための通常のキーが使えなくなります。どうしても答えが必要なら、Escapeキーを止めるのではなく、答えごとにTabキーで届くボタンを用意します。
×を常に表示する。 ポインターが乗ったときだけ現れる閉じるボタンは、それまで隠されています。visibility: hidden や display: none で隠していると、フォーカスを受け取れないため、Tabキーが届きません。ポップアップが表示されている間は、×も表示しておきます。「登録する」の横に置く「今回はやめておく」のボタンは、誰にでも見える二つ目の出口で、×が暗に示すだけのことをはっきり言葉にします。
背景のクリック。 ポップアップの周りの暗くなった部分をクリックして閉じる仕組みは、ポインターのための便利な機能です。キーボードの出口にはならないため、ボタンやEscapeキーの代わりにはなりません。
最後のコントロールの次のTab。 フォーカスを留める処理がないと、HTML標準のダイアログは「登録する」の次のTabでフォーカスをアドレスバーなどブラウザー自身のコントロールへ移し、もう一度Tabを押すとダイアログの最初のコントロールに戻します。WCAGの達成方法H102が説明している動作で、キーボードトラップではありませんが、フォーカスはポップアップからもページからも出てしまいます。フォーカスを留める処理は、ARIA Authoring Practicesのモーダルダイアログのパターンと同じように、フォーカスをポップアップの中に保ちます。処理が集める一覧は、ダイアログの中身と合わせておきます。一覧に当てはまらないコントロールを加えると、Tabが1周するときに飛ばされます。
フォーカスを留める処理だけを外さない。 背後のページを操作できない状態にしないまま、Tabキーを内側に留める処理だけを外しても、問題は解決しません。Tabキーはオーバーレイの下の、フォーカスが見えにくいページを移動するようになり、×には相変わらず届きません。モーダルの中のフォーカスについては、背後にフォーカスが残るモーダルで扱っています。
閉じた後のフォーカスの位置。 自動で開くポップアップは、利用者がそのときいた場所、たとえばフォームの途中からフォーカスを奪います。フォーカスはそこへ戻し、利用者が続きから操作できるようにします。showModal() なら自動で戻り、独自の修正では、そのための処理を書いています。
チャットのパネル。 サポートチャットの閉じるボタンと起動ボタンには、別の問題があります。キーボードで開けず、閉じるボタンにも名前がないチャットを参照してください。
修正の確認
5項目、マウス不要
確認方法: キーボード、スクリーンリーダー
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。ポップアップ、それを出すツール、またはその設定を変更した際は、再度確認します。
スクリーンリーダーでの <dialog>の読まれ方
制限
このサンプルは一つのポップアップのための出発点で、あらゆるオーバーレイで同じ結果になることを保証するものではありません。一度閉じたポップアップをページごとに開き直さないための記録と、subscribe() がアドレスをどう扱うかは省略しています。ポップアップが開く前にどこにもフォーカスがなかった場合、閉じた後にフォーカスをどこへ移すかはページによります。ブラウザーに任せるのも妥当ですが、その後のTabがどこから進むかはブラウザーによって異なるため、対象のブラウザーで確認してください。実際に利用者が使う支援技術で、操作全体を確認してください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
EaseWebのサービス
修正した問題を、次のリリースで再発させない
このガイドのような修正方法を、コーディングエージェントが従うルールと、CIのチェックに組み込みます。あわせて定期的に、キーボードとスクリーンリーダーで手動の再テストを行います。