APGのページを参考に、アクセシビリティを考慮したモーダルの実装を試してみます。
対応前
まずは特に考慮しない場合の実装です。
過去に作成したモーダルの処理がベースになります。
<button data-modal-link="modal-a">モーダルAを開く</button> <button data-modal-link="modal-b">モーダルBを開く</button> <div class="modal-bg js-modalclose"></div> <div class="modal" data-modal-content="modal-a"> <h3>モーダルAのタイトル</h3> <p>モーダルAのコンテンツです。<a href="#">詳しくはヘルプページ</a>をご確認ください。</p> <button class="js-modalclose">閉じる</button> </div> <!-- END modal-a --> <div class="modal" data-modal-content="modal-b"> <p>モーダルBのコンテンツです。</p> <button class="js-modalclose">閉じる</button> </div> <!-- END modal-b -->
data属性を使って、モーダルのリンクとコンテンツの紐づけを行う想定です。
次にCSSですが、モーダルに必要な部分のみ抜粋しています。
.modal-bg { display: none; } .modal-bg.is-show { display: block; } .modal { display: none; } .modal.is-show { display: block; }
.is-showの付け替えで表示と非表示を切り替えます。
最後にJavaScriptです。
const modalLinks = document.querySelectorAll('[data-modal-link]'); const modalCloseLinks = document.querySelectorAll('.js-modalclose'); // モーダルを開く modalLinks.forEach(function(element) { element.addEventListener('click', function(e) { e.preventDefault(); const target = e.currentTarget.getAttribute('data-modal-link'); document.querySelector(`[data-modal-content = "${target}"]`).showModal(); }); });
これで簡単ですが、モーダルの実装ができました。
対応前のデモページ
このモーダルをベースに、アクセシビリティを考慮してみます。
要件
変更する前に、ARIA Authoring Practices Guide (APG)のDialog (Modal) Patternのページを参考に考慮する内容を確認します。
モーダルパターンについて
Windows under a modal dialog are inert.
That is, users cannot interact with content outside an active dialog window.
アクティブなモーダルの外側は不活性で、ユーザーは外側のコンテンツを操作できない。
Like non-modal dialogs, modal dialogs contain their tab sequence.
That is, Tab and Shift + Tab do not move focus outside the dialog.
However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog.
TabキーとShift+Tabキーでモーダルの外側にフォーカスを移動させない。
キーボード操作
- When a dialog opens, focus moves to an element inside the dialog.
- When a dialog opens, focus moves to an element contained in the dialog.
Generally, focus is initially set on the first focusable element.
However, the most appropriate focus placement will depend on the nature and size of the content.
Examples include:
モーダルを開いた際、フォーカスをモーダル内の要素に移動する。
フォーカス対象はモーダルコンテンツの性質や量にもよるが、基本的に最初のフォーカス可能な要素にフォーカスする。
- Tab:
- Moves focus to the next tabbable element inside the dialog.
- If focus is on the last tabbable element inside the dialog, moves focus to the first tabbable element inside the dialog.
Tabキーでモーダル内の次のフォーカス可能な要素にフォーカスを移動する。
モーダル内の最後のフォーカス可能な要素にフォーカスがある場合、モーダル内の最初のフォーカス可能な要素にフォーカスを移動する。
- Shift + Tab:
- Moves focus to the previous tabbable element inside the dialog.
- If focus is on the first tabbable element inside the dialog, moves focus to the last tabbable element inside the dialog.
Shiftキー+Tabキーで前のフォーカス可能な要素にフォーカスを移動する。
モーダル内の最初のフォーカス可能な要素にフォーカスがある場合、モーダル内の最後のフォーカス可能な要素にフォーカスを移動する。
- Escape: Closes the dialog.
Escキーでモーダルを閉じる。
- When a dialog closes, focus returns to the element that invoked the dialog unless either:
モーダルを閉じる際、フォーカスは基本的にモーダルを呼び出した要素に戻す。
- It is strongly recommended that the tab sequence of all dialogs include a visible element with role
button
that closes the dialog, such as a close icon or cancel button.
モーダルの中に、モーダルを閉じるためのbuttonのロールを持つ要素を含める。
WAI-ARIA
- The element that serves as the dialog container has a role of dialog.
- All elements required to operate the dialog are descendants of the element that has role
dialog
.- The dialog container element has aria-modal set to
true
.
モーダルの要素にはrole=dialogとaria-modal=trueを設定する。
- The dialog has either:
- A value set for the aria-labelledby property that refers to a visible dialog title.
- A label specified by aria-label.
モーダル要素に (1)aria-labelledbyでモーダルのタイトルを参照 (2)aria-labelでタイトルを指定 のどちらかを設定する。
- Optionally, the aria-describedby property is set on the element with the
dialog
role to indicate which element or elements in the dialog contain content that describes the primary purpose or message of the dialog.
Specifying descriptive elements enables screen readers to announce the description along with the dialog title and initially focused element when the dialog opens, which is typically helpful only when the descriptive content is simple and can easily be understood without structural information.
It is advisable to omit specifyingaria-describedby
if the dialog content includes semantic structures, such as lists, tables, or multiple paragraphs, that need to be perceived in order to easily understand the content, i.e., if the content would be difficult to understand when announced as a single unbroken string.
オプションとして、モーダルの要素にaria-describedbyを設定して、モーダル内のどの要素が主な目的やメッセージを説明するコンテンツ化を示す。
これらをまとめると、以下の内容になります。
- モーダルを開いた際、フォーカスをモーダル内の最初のフォーカス可能な要素にフォーカスする。
- モーダル内でのキーボード操作時、フォーカスをモーダルの外側へ移動させない。
- Tabキー/Shiftキー+Tabキーで、モーダル内の次/前のフォーカス可能な要素にフォーカスを移動する。
モーダル内の最後の場合は最初、最初の場合は最後に移動する。 - Escキーでモーダルを閉じる。
- モーダルを閉じる際、フォーカスはモーダルを呼び出した要素に戻す。
- モーダルの中に、モーダルを閉じるためのbuttonのロールを持つ要素を含める。
- モーダル要素にrole=dialogとaria-modal=trueを設定する。
- モーダル要素にaria-labelledbyまたはaria-labelのどちらかを設定する。
- モーダル要素にaria-describedbyを設定する(オプション)。
dialog要素
上記要件を踏まえて、モーダルをdialog要素を使った形に変更してみます。
dialog要素の使い方は過去に記事を投稿していますので、詳しくはそちらもご確認ください。
<button data-modal-link="modal-a">モーダルAを開く</button> <button data-modal-link="modal-b">モーダルBを開く</button> <dialog class="modal" data-modal-content="modal-a"> <h3>モーダルAのタイトル</h3> <p>モーダルAのコンテンツです。<a href="#">詳しくはヘルプページ</a>をご確認ください。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-a --> <dialog class="modal" data-modal-content="modal-b"> <p>モーダルBのコンテンツです。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-b -->
dialog要素への変更と合わせて、モーダル内の閉じるボタンの実装を変更しました。
次にCSSです。
.modal { padding: 0; border: none; width: 600px; } .modal::backdrop { background: rgba(0, 0, 0, 0.75); }
最後にJavaScriptです。
const modalLinks = document.querySelectorAll('[data-modal-link]'); const modalCloseLinks = document.querySelectorAll('.js-modalclose'); // モーダルを開く modalLinks.forEach(function(element) { element.addEventListener('click', function(e) { e.preventDefault(); const target = e.currentTarget.getAttribute('data-modal-link'); document.querySelector(`[data-modal-content = "${target}"]`).showModal(); }); });
モーダルの表示をshowModal()を使用する形に変更しました。
dialog要素に変更後のデモページ
この変更で、以下のキーボード操作やフォーカスに関する対応ができました。
- モーダルを開いた際、フォーカスをモーダル内の最初のフォーカス可能な要素にフォーカスする。
- モーダル内でのキーボード操作時、フォーカスをモーダルの外側へ移動させない。
- Tabキー/Shiftキー+Tabキーで、モーダル内の次/前のフォーカス可能な要素にフォーカスを移動する。
モーダル内の最後の場合は最初、最初の場合は最後に移動する。 - Escキーでモーダルを閉じる。
- モーダルを閉じる際、フォーカスはモーダルを呼び出した要素に戻す。
- モーダルの中に、モーダルを閉じるためのbuttonのロールを持つ要素を含める。
WAI-ARIA
残りの以下要件の対応を行っていきます。
- モーダル要素にrole=dialogとaria-modal=trueを設定する。
- モーダル要素にaria-labelledbyまたはaria-labelのどちらかを設定する。
- モーダル要素にaria-describedbyを設定する(オプション)。
このうちの1つ目に関しては、dialog要素に変更したことでrole=dialogとなっています。
また、モーダルを開く際にshowModal()を使っているので、暗黙的にaria-modal=trueとなっています。
<dialog>
要素は、ARIA の role=”dialog” 属性を使用したカスタムダイアログと同じような形で、ブラウザーが提供します。<dialog>
要素がshowModal()
メソッドで呼び出された場合、暗黙のうちに aria-modal=”true” となり、一方<dialog>
がshow()
メソッド、またはopen
属性を使用して表示されたり<dialog>
の既定のdisplay
を変更した場合は[aria-modal="false"]
として表示されます。モーダルダイアログを実装する際には、<dialog>
とそのコンテンツ以外はinert
属性を使って不活性化する必要があります。<dialog>
をHTMLDialogElement.showModal()
メソッドで使用した場合、この動作はブラウザーが提供します。引用 – <dialog>: ダイアログ要素 – HTML: ハイパーテキストマークアップ言語 | MDN
そのため、aria-labelledby / aria-label / aria-describedby(オプション) の設定を行います。
<button data-modal-link="modal-a">モーダルAを開く</button> <button data-modal-link="modal-b">モーダルBを開く</button> <dialog class="modal" data-modal-content="modal-a" aria-labelledby="modal-a-title" aria-describedby="modal-a-desc" > <h3 id="modal-a-title">モーダルAのタイトル</h3> <p id="modal-a-desc">モーダルAのコンテンツです。<a href="#">詳しくはヘルプページ</a>をご確認ください。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-a --> <dialog class="modal" data-modal-content="modal-b" aria-label="モーダルBのタイトル" > <p>モーダルBのコンテンツです。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-b -->
最後に、参考にしたページだと特に記載されていなかったのですが、モーダルを開くボタンに対して、ダイアログを開くボタンである旨や開く対象要素の設定がある方がよいと思うので、aria-haspopup=dialogとaria-controlsを追加しておきます。
<button aria-haspopup="dialog" aria-controls="modal-a" data-modal-link="modal-a" > モーダルAを開く </button> <button aria-haspopup="dialog" aria-controls="modal-b" data-modal-link="modal-b" > モーダルBを開く </button> <dialog id="modal-a" class="modal" data-modal-content="modal-a" aria-labelledby="modal-a-title" aria-describedby="modal-a-desc" > <h3 id="modal-a-title">モーダルAのタイトル</h3> <p id="modal-a-desc">モーダルAのコンテンツです。<a href="#">詳しくはヘルプページ</a>をご確認ください。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-a --> <dialog id="modal-b" class="modal" data-modal-content="modal-b" aria-label="モーダルBのタイトル" > <p>モーダルBのコンテンツです。</p> <form method="dialog"> <button>閉じる</button> </form> </dialog> <!-- END modal-b -->
aria-haspopup=dialogとaria-controls追加のデモページ
これでアクセシビリティを考慮したモーダルの実装ができました。
コメントが承認されるまで時間がかかります。