トピックを検索
キーボードで開けず、階層も伝わらないツリービュー
セクションとページが並ぶサイドバーを、クリックできるdivで作っているか、ツリーのロールを付けても、そのロールが約束するキー操作、状態、階層がないため、キーボードではセクションを開けず、スクリーンリーダーにはすべての項目が同じ階層に聞こえます。
<div class="section">アカウント</div> 問題
キーボード利用者はサイドバーのセクションを開いて中のページにたどり着けず、スクリーンリーダー利用者には、ページがどのセクションにあるか、どの深さにあるか、セクションが開いているかが分かりません。
あるアプリのヘルプセンターには、トピックを「はじめに」「請求」「アカウント」の3つのセクションに分けたサイドバーがあります。セクションをクリックすると、その下にページが字下げされて開き、矢印が下を向きます。「請求」の中の「支払い方法」もセクションで、1段深いところに「カード」と「銀行振込」があります。セクションは、クリック処理を付けたdivです。マウスを使わずにTabを押すと、たまたま表示されているリンク、「プランと料金」と「請求書」を順に移動し、サイドバーを出て「サポートに問い合わせる」へ進みます。「はじめに」「支払い方法」「アカウント」は開けないため、その中のページにはたどり着けません。スクリーンリーダーには表示中のリンクが次々に読まれるだけで、それが「請求」の中にあることは伝わらず、セクション名は開けることを示すものがないただの文字として読まれます。
そこで、チームは手早い方法でサイドバーをツリーにします。サイドバーに role="tree"、すべてのセクションとリンクに role="treeitem" を付け、それ以外は何もしません。キーボードにとっては何も変わらず、Tabキーは今もすべてのセクションを飛ばします。スクリーンリーダーはサイドバーをツリーと伝えるようになりますが、その中で矢印キーは何もせず、すべての項目が「請求書、ツリーアイテム、階層1」のように階層1で読まれ、どのセクションも開いているかどうかを伝えません。
達成基準2.1.1「キーボード」は、マウスでできることはすべてキーボードでもできることを求めています。セクションを開く方法は、フォーカスを受けないdivのクリックだけのため、閉じたセクションの中のページにはマウスなしでは届きません。ツリーのロールを足しても、ここは何も変わりません。セクションは今もフォーカスを受けず、ツリーが約束する矢印キーは何もしません。
達成基準4.1.2「名前(name)・役割(role)及び値(value)」は、操作部品が役割と状態を公開することを求めています。divのセクションはどちらも公開していません。押せることを示す役割も、開閉の状態もありません。ツリーのセクションは役割を持っていても aria-expanded がないため、スクリーンリーダーでは開いたセクションと閉じたセクションの区別も、セクションとページの区別もつきません。
達成基準1.3.1「情報及び関係性」は、画面上の構造がコードにもあることを求めています。字下げは、「請求書」が「請求」の中にあり、「カード」がさらに1段深いことを示しています。divのサイドバーでは、マークアップのどこにもそれが書かれていません。ツリーのロールがあると、ブラウザーは group 要素の入れ子から各項目の階層を求めますが、group がないため、すべての項目が階層1になります。ロールを付けたことで、間違った構造が正式なものになります。
ヘルプセンター、ドキュメント、アカウント設定では、すべてのページをこのようなサイドバーの中に置くことがよくあります。狭い画面では同じサイドバーがメニューに移ることが多く、問題もそのまま付いていきます。開けないセクションは、クリックできない人からその中のページを隠します。
10グループ中2グループに影響
クリック処理を付けたdivには役割がないため、最初のサイドバーではaxeが確認することはありません。ツリーのロールはaxeに合格します。どの treeitem も tree の中にあり、どれにも名前があるからです。axeが確かめるのはツリーのロールの形(tree や group の外の treeitem は aria-required-parent として報告し、項目のない tree は aria-required-children の確認対象として送る)で、キーが何かをするか、階層が字下げと合っているかは確かめません。
この診断ツールはキーを押します。項目がTabの移動先を1つだけ共有するツリーでは、その項目にフォーカスを置いて矢印キーを押し、フォーカスがまったく動かなければ、ほかの項目に届かないとして報告します。group が隠れているのに aria-expanded のないセクションの treeitem も報告します。複数の項目がTabの移動先で矢印キーが何もしないツリー、group も aria-level もないのに項目が画面上で字下げされているツリー、ツリーと名付けた要素の中にdivで作られ、Tabが届かないクリックできる行があるものは、人が確認する項目として渡します。それと分かるクラス名のないdivのサイドバーは見つからず、階層が聞く人にとって筋が通っているかは、どの確認でも分かりません。
2つ目のサイドバーは、AIの修正で生まれることがあるものです。今あるdivに role="tree" と role="treeitem" を加えて終え、その結果はaxeに合格します。すべての行に tabindex="0" を付ける修正や、aria-level だけを加えて aria-expanded を付けない修正もあります。
試してみる
キーボードだけで「アカウント」を開き、「メールアドレスを変更する」まで移動してください。
- 問題1:Tabキーを押します。止まるのは表示中のリンクだけで、セクションには止まらないため、「アカウント」を開けません。
- 問題2:同じdivにツリーのロールを足しただけです。Tabキーは同じようにセクションを飛ばし、矢印キーは何もせず、下の欄ではすべての項目が階層1と読まれます。
- 修正を適用します。ツリービューでは、矢印キーで移動してセクションを開け、HomeとEndで端へ移動できます。
問題あり 修正できますか?
スクリーンリーダーの読み上げ:
(Tabでサイドバーへ移動)
修正方法
すべてのセクションを、キーボードで開けて開閉状態を伝える操作部品にし、そのページをマークアップ上でもセクションの中に入れます。
- 1どのセクションもキーボードで開け、開いているかどうかを伝える。
- 2ページのセクションと深さは、字下げだけでなくマークアップで示す。
- 3role="tree" には、ロールが約束するもの、つまりTabの移動先1つ、矢印キー、階層、状態をすべて持たせる。持たせないなら、リストとボタンを使う。
クリックできるdivで作ったセクション
<div class="help-nav"> <div class="section">はじめに</div> <div class="pages" hidden> <a href="/help/create-account">アカウントを作成する</a> <a href="/help/invite-your-team">チームを招待する</a> </div> <div class="section is-open">請求</div> <div class="pages"> <a href="/help/plans">プランと料金</a> <div class="section">支払い方法</div> <div class="pages" hidden> <a href="/help/cards">カード</a> <a href="/help/bank-transfer">銀行振込</a> </div> <a href="/help/invoices">請求書</a> </div> <div class="section">アカウント</div> <div class="pages" hidden> <a href="/help/change-email">メールアドレスを変更する</a> <a href="/help/delete-account">アカウントを削除する</a> </div></div>セクションごとにボタンを置いた、入れ子のリスト
<nav class="help-nav" aria-label="ヘルプのトピック"> <ul> <li> <button type="button" aria-expanded="false" aria-controls="help-start">はじめに</button> <ul id="help-start" hidden> <li><a href="/help/create-account">アカウントを作成する</a></li> <li><a href="/help/invite-your-team">チームを招待する</a></li> </ul> </li> <li> <button type="button" aria-expanded="true" aria-controls="help-billing">請求</button> <ul id="help-billing"> <li><a href="/help/plans">プランと料金</a></li> <li> <button type="button" aria-expanded="false" aria-controls="help-payment">支払い方法</button> <ul id="help-payment" hidden> <li><a href="/help/cards">カード</a></li> <li><a href="/help/bank-transfer">銀行振込</a></li> </ul> </li> <li><a href="/help/invoices">請求書</a></li> </ul> </li> <li> <button type="button" aria-expanded="false" aria-controls="help-account">アカウント</button> <ul id="help-account" hidden> <li><a href="/help/change-email">メールアドレスを変更する</a></li> <li><a href="/help/delete-account">アカウントを削除する</a></li> </ul> </li> </ul></nav>この方法を選ぶ理由:まずHTMLの要素を使う
セクションは click にしか反応せず、フォーカスも受けないため、閉じたセクションのページにはキーボードで届きません。セクションが開けること、開いているかどうか、後に続くリンクがそのセクションに属することは、マークアップのどこにも書かれていません。入れ子は余白でしかありません。
各セクションはHTML標準のボタンのため、Tabで届き、EnterキーとSpaceキーで開けます。キー操作を自分で書く必要はありません。aria-expanded により、スクリーンリーダーは「請求、ボタン、展開」や「折りたたみ」と読み上げます。各セクションのページは、そのリスト項目の中に入れ子にしたリストのため、構造がマークアップにあります。スクリーンリーダーはリストに入るとリストであることと項目数を伝え、入れ子の深さも伝えるものもあります。hidden により閉じたセクションのリンクはTabの順序から外れ、開いたセクションのリストはボタンのすぐ後にあるため、次のTabでその中へ進みます。ARIA Authoring Practicesが、開閉するリンクのまとまりを持つナビゲーションに勧めている、ディスクロージャーのパターンです。
どちらも基準を満たします。この例のようなページのサイドバーには、入れ子のリストを使ってください。どのブラウザー、スクリーンリーダー、音声操作のソフトも知っているリンクとボタンで、スクリプトは数行で済み、ページのほかの部分と同じようにTabで移動できます。ARIA Authoring Practicesも同じことを述べています。ツリーによるナビゲーションの例そのものに、ツリーのロールに必要なキー操作を必要とするサイトは少なく、一般的なサイトのナビゲーションにはディスクロージャーのパターンが合っていると書かれています。ツリーを作るのは、サイドバーがアプリケーションのウィジェットで、ページへ移動するのではなく項目を選んだり操作したりする場合です。フォルダーの選択やファイルマネージャーのように、項目が多くTabで1つずつ移動していられない場合もそうです。そのときは、ロール、状態、Tabの移動先1つ、すべてのキーをそろえます。
開いているページを示します。リストでは、そのリンクに aria-current="page" を付けます。現在のページを見た目だけで示すナビゲーションで説明しているとおりです。ツリーでは、その treeitem に付け、読み込み時にその上のセクションを開いておきます。そうすれば、現在のページが見え、Tabで最初にそこへ届きます。aria-selected は、フォルダーを選んでから「移動」を押すときのように、項目を選ぶことと、そこへ移動することが別の段階になるツリーのためのものです。リンクのツリーには要りません。複数の項目を選べるツリーには、ARIA Authoring Practicesは代わりに aria-checked を勧めています。
ブラウザーが階層を数えられるのは、見える入れ子からだけです。長い、仮想化している、枝を必要なときに読み込むといった理由で、平らに描くツリーや一部だけを描くツリーでは、すべての項目に aria-level、aria-setsize、aria-posinset が必要です。Authoring Practicesは、テストでブラウザーとスクリーンリーダーが間違った値を伝える場合にも、これらを明示するよう勧めています。
ツリービューのパターンは、文字を押すとその文字で始まる次の項目へ移動する、先頭文字での移動も勧めています。すべてのツリーに、特に最上位に8つ以上の項目があるツリーに勧められています。このサイドバーは3つです。フォーカスのある項目と同じ階層のセクションをすべて開くアスタリスクは任意です。閉じたセクションで右キーを押すと、フォーカスは動かずにセクションが開きます。パターンが求めるとおりで、利用者は今の場所にとどまれます。もう一度右キーを押すと中へ移動します。
treeitem の中にリンクやボタンを置かないでください。項目そのものが操作部品で、その中の操作部品は、どこに属するかを伝えない2つ目のTabの移動先になります。リストでボタンとリンクを分けているのは、そのためです。狭い画面では、同じリストかツリーをメニューのパネルに入れます。そこで別のウィジェットに作り直さないでください。ライブラリーのツリーの部品はさまざまです。使う前に、グループを入れ子にしているか階層を設定しているか、すべてのキーを扱っているかを確かめます。
AI コーディングアシスタントで修正しますか? このガイドを Markdown で取得、または全ガイドをコーディングエージェントで使う。
修正の確認
マウスを使わない5つの確認
確認方法: キーボード、スクリーンリーダー、強制配色、ズーム
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。サイドバー、その部品、ライブラリーを変更した際は、再度確認します。
制限
このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。スクリプトは最小限の例で、ツリーには先頭文字での移動、複数選択、必要なときの読み込みがありません。ページ上部に横に並ぶドロップダウンメニューは別のパターンで、マウスでしか開かないドロップダウンメニューで扱っています。1つだけ開閉する部分は、開いているか伝わらない表示・非表示ボタンで扱っています。項目をドラッグで移動できるファイルのツリーには、ドラッグしない方法も必要です。マウスでしか動かせないドラッグ&ドロップのボードを参照してください。スレッドの受信トレイのように行に複数の列があるものはツリーグリッドで、別のパターンです。対象とする支援技術で、一連の操作全体をテストしてください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
参考資料
- ARIA APG: treeview(新しいタブで開きます)
- ARIA APG: treeview-navigation(新しいタブで開きます)
- ARIA APG: disclosure-navigation(新しいタブで開きます)
- ARIA APG: keyboard-interface(新しいタブで開きます)
- WCAG Understanding: info-and-relationships(新しいタブで開きます)
- WCAG Understanding: keyboard(新しいタブで開きます)
- WCAG Understanding: name-role-value(新しいタブで開きます)
- w3.org: wai-aria-1.2(新しいタブで開きます)
コードを更新しても、使いやすさを保つために
ガイドの内容を、コーディングエージェントやCIで使える開発ルールにまとめます。更新後は、キーボードとスクリーンリーダーで再テストし、問題が再発していないか確認します。