アクセシブルなメニューシステムの構築
公開: 2022-03-10編集者注:この記事は、もともと包括的コンポーネントに掲載されていました。 同様の包括的なコンポーネントの記事について詳しく知りたい場合は、Twitterで@inclusicompsをフォローするか、RSSフィードを購読してください。 Patreonでinclusive-components.designをサポートすることで、利用可能な堅牢なインターフェースコンポーネントの最も包括的なデータベースにすることができます。
分類は難しいです。 カニを例にとってみましょう。 ヤドカリ、カニダマシ、カブトガニは、分類学的に言えば、真のカニではありません。 しかし、それは「カニ」接尾辞の使用を止めるものではありません。 時間が経つにつれて、そして発癌と呼ばれるプロセスのおかげで、偽のカニが真のカニにより近くなるように進化するとき、それはより混乱します。 これは、過去にヤドカリであったと考えられているタラバガニの場合です。 彼らの殻のサイズを想像してみてください!
デザインでは、異なるものに同じ名前を付けるという同じ間違いを犯すことがよくあります。 それらは似ているように見えますが、見た目は欺くことができます。 これは、コンポーネントライブラリの明確さに不幸な影響を与える可能性があります。 インクルージョンに関しては、意味的および動作的に不適切なコンポーネントを再利用する可能性もあります。 ユーザーはあることを期待し、別のことを得るでしょう。
「ドロップダウン」という用語は、典型的な例を示しています。 <select>
要素からの<option>
のセットや、ナビゲーションサブメニューを構成するJavaScriptで公開されたリンクのリストなど、インターフェイスには多くのものが「ドロップダウン」します。 同名; まったく違うもの。 (もちろん、これらを「プルダウン」と呼ぶ人もいますが、それには入りません。)
オプションのセットを構成するドロップダウンは「メニュー」と呼ばれることが多いので、ここで説明します。 真のメニューを考案しますが、途中で真ではないメニューについては多くのことが言われます。
クイズから始めましょう。 イラストのナビゲーションバーからぶら下がっているリンクのボックスはメニューですか?
答えはノーで、本当のメニューではありません。
ナビゲーションスキーマはリンクのリストで構成されるというのは長年の慣習です。 ほぼ長年の慣習により、サブナビゲーションはリンクのネストされたリストとして提供する必要があります。 上に示したコンポーネントのCSSを削除すると、青色とTimes New Romanを除いて、次のように表示されます。
意味的に言えば、リンクのネストされたリストはこのコンテキストでは正しいです。 ナビゲーションシステムは実際にはコンテンツのテーブルであり、これがコンテンツのテーブルの構造です。 「メニュー」を本当に考えさせる唯一のことは、ネストされたリストのスタイルと、ホバーまたはフォーカスでそれらが表示される方法です。
ここで問題が発生し、WAI-ARIAセマンティクスの追加が開始されます: aria-haspopup="true"
、 role="menu"
、 role="menuitem"
など。これから説明するように、これらの場所がありますが、ここでは説明しません。 。 理由は次の2つです。
- ARIAメニューは、ナビゲーション用ではなく、アプリケーションの動作用に指定されています。 デスクトップアプリケーションのメニューシステムを想像してみてください。
- トップレベルのリンクはリンクとして使用できる必要があります。つまり、メニューボタンのようには動作しません。
(2)について:サブメニューのあるナビゲーション領域をトラバースする場合、「トップレベル」リンク(図の「ショップ」)にカーソルを合わせるかフォーカスすると、各サブメニューが表示されると予想されます。 これにより、サブメニューが表示され、独自のリンクがフォーカス順に配置されます。 フォーカスイベントとブラーイベントをキャプチャして必要なときにサブメニューの外観を維持するJavaScriptの助けを少し借りれば、キーボードを使用している人は、各層の各リンクを順番にタブで移動できるはずです。
aria-haspopup="true"
プロパティを取得するメニューボタンは、このようには動作しません。 それらはクリックするとアクティブになり、秘密のメニューを表示する以外の目的はありません。
写真のように、そのメニューが開いているか閉じているかは、 aria-expanded
と通信する必要があります。 この状態は、フォーカスではなく、クリック時にのみ変更する必要があります。 ユーザーは通常、単なるフォーカスイベントでの明示的な状態変化を期待していません。 私たちのナビゲーションシステムでは、状態は実際には変化しません。 それは単なるスタイリングのトリックです。 動作的には、そのような表示/非表示のトリックが発生していないかのように、ナビゲーションをタブで移動できます。
ナビゲーションサブメニューの問題
ナビゲーションサブメニュー(または一部の「ドロップダウン」)は、マウスまたはキーボードでうまく機能しますが、タッチに関してはそれほど熱くはありません。 この例のトップレベルの「ショップ」リンクを初めて押すと、サブメニューを開いてリンクをたどるように指示していることになります。
ここには2つの可能な解決策があります。
- トップレベルリンク(
e.preventDefault()
)とスクリプトのデフォルトの動作を完全なWAI-ARIAメニューのセマンティクスと動作で防止します。 - サブメニューの代わりに、各トップレベルの宛先ページに目次があることを確認してください。
(1)は不十分です。前述したように、これらの種類のセマンティクスと動作は、リンクがサブジェクトコントロールであるこのコンテキストでは予期されないためです。 さらに、トップレベルのページが存在する場合、ユーザーはそのページに移動できなくなりました。
補足:タッチデバイスはどのデバイスですか?
「これは優れたソリューションではありませんが、タッチインターフェイスにのみ追加します」と考えたくなります。 問題は、デバイスにタッチスクリーンがあるかどうかをどのように検出するかということです。
あなたは確かに「小さな画面」を「タッチ起動」と同一視すべきではありません。 美術館のタッチディスプレイを作っている人たちと同じオフィスで働いていたので、周りの最大のスクリーンのいくつかはタッチスクリーンであると確信できます。 デュアルキーボードとタッチ入力のラップトップもますます多作になっています。
同様に、すべてではありませんが多くの小さなデバイスがタッチデバイスです。 包括的設計では、仮定を立てる余裕はありません。
解像度(2)は、すべての入力のユーザーに「フォールバック」を提供するという点で、より包括的で堅牢です。 しかし、ここでのフォールバック用語に関する恐ろしい引用符は、ページ内のコンテンツの表がナビゲーションを提供する優れた方法であると実際に考えているため、非常に慎重です。
受賞歴のある政府デジタルサービスチームは同意しているように見えます。 ウィキペディアでも見たことがあるかもしれません。
コンテンツの表
コンテンツのテーブルは、関連するページまたはページセクションのナビゲーションであり、 <nav>
要素、リスト、およびグループラベル付けメカニズムを使用して、メインサイトのナビゲーション領域と意味的に類似している必要があります。
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->
ノート
- この例では、ドロップダウンサブメニューにあるように、各セクションが独自のページであると想像しています。
- これらの「ショップ」ページのそれぞれが同じ構造を持ち、この「製品」の目次が同じ場所にあることが重要です。 一貫性は理解をサポートします。
- リストはアイテムをグループ化し、スクリーンリーダーの合成音声などの支援技術の出力にそれらを列挙します。
-
<nav>
は、aria-labelledby
を使用して見出しによって再帰的にラベル付けされます。 これは、「製品ナビゲーション」がタブでリージョンに入ると、ほとんどのスクリーンリーダーでアナウンスされることを意味します。 また、「製品ナビゲーション」はスクリーンリーダー要素のインターフェイスに項目化され、ユーザーはそこから地域に直接移動できます。
すべて1ページに
長すぎてスクロールするのが面倒になることなく、すべてのセクションを1つのページに収めることができれば、さらに良いでしょう。 各セクションのハッシュ識別子にリンクするだけです。 たとえば、 href="#waffle-irons"
は。
<nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->
(注:一部のブラウザーは、リンクされたページフラグメントに実際にフォーカスを送信するのが不十分です。ターゲットフラグメントにtabindex="-1"
を配置すると、これが修正されます。)
サイトに多くのコンテンツがある場合、コンテンツのテーブルの「メニュー」を自由に使用して表現された、注意深く構築された情報アーキテクチャは、不安定で扱いにくいドロップダウンシステムよりもはるかに望ましいものです。 レスポンシブにするのが簡単で、必要なコードが少ないだけでなく、物事がより明確になります。ドロップダウンシステムが構造を隠している場合、コンテンツのテーブルがそれをむき出しにします。
政府デジタルサービスのgov.ukを含む一部のサイトには、単なるコンテンツの表であるインデックス(または「トピック」)ページが含まれています。 これは非常に強力な概念であるため、人気のある静的サイトジェネレーターHugoはデフォルトでそのようなページを生成します。
情報アーキテクチャは、インクルージョンの大きな部分です。 うまく整理されていないサイトは、技術的には好きなだけ準拠できますが、それでも多くのユーザー、特に認知障害のあるユーザーや時間に追われているユーザーを遠ざけることになります。
ナビゲーションメニューボタン
私たちは偽のナビゲーション関連のメニューについて話しているが、ナビゲーションメニューのボタンについて話さないのは私には許されないだろう。 これらは、3行の「ハンバーガー」または「ナビコン」アイコンで示されているのをほぼ確実に見たことがあるでしょう。
情報アーキテクチャが簡素化され、ナビゲーションリンクが1層しかない場合でも、小さな画面のスペースは貴重です。 ボタンの後ろにナビゲーションを非表示にすると、ビューポートのメインコンテンツ用のスペースが増えます。
ナビゲーションボタンは、これまでに研究した真のメニューボタンに最も近いものです。 クリック時にメニューの可用性を切り替える目的があるため、次のようにする必要があります。
- リンクではなく、ボタンとして自分自身を識別します。
- 対応するメニューの展開状態または折りたたみ状態を特定します(厳密には、リンクのリストにすぎません)。
プログレッシブエンハンスメント
しかし、自分より先に進まないようにしましょう。 プログレッシブエンハンスメントに注意し、JavaScriptなしでこれがどのように機能するかを検討する必要があります。
拡張されていないHTMLドキュメントでは、ボタンを使用してできることは多くありません(送信ボタンを除きますが、ここで達成したいこととは密接に関連していません)。 代わりに、ナビゲーションに移動するためのリンクから始める必要がありますか?
<a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
リンクとナビゲーションの間に多くのコンテンツがない限り、リンクを持つことに多くの意味はありません。 サイトナビゲーションはほとんどの場合、ソースオーダーの上部近くに表示されるはずなので、必要はありません。 したがって、実際には、JavaScriptがない場合のナビゲーションメニューは、ナビゲーションである必要があります。
<nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>
これを強化するには、ボタンを初期状態で追加し、ナビゲーションを非表示にします( hidden
属性を使用)。
<nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
一部の古いブラウザ(どのブラウザかはご存知でしょう)はhidden
をサポートしていないため、CSSに次のように配置することを忘れないでください。 display: none
支援技術からメニューを非表示にしたり、フォーカスの順序からリンクを削除したりするのと同じ効果があるため、問題が修正されます。
[hidden] { display: none; }
もちろん、古いソフトウェアをサポートするために最善を尽くすことは、包括的な設計の行為です。 アップグレードできない、またはアップグレードしたくない人もいます。
配置
多くの人がうまくいかないのは、ボタンを地域の外に置くことです。 これは、ショートカットを使用して<nav>
に移動するスクリーンリーダーのユーザーは、それが空であることに気付くということを意味しますが、これはあまり役に立ちません。 リストがスクリーンリーダーから隠されていると、彼らはこれに遭遇するでしょう:
<nav> </nav>
状態を切り替える方法は次のとおりです。
var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });
aria-controls
Aria-controls Is Poopで書いたように、スクリーンリーダーのユーザーが制御要素から制御要素に移動できるようにすることを目的としたaria-controls
属性は、JAWSスクリーンリーダーでのみサポートされます。 だからあなたは単にそれに頼ることはできません。
要素間でユーザーを誘導するための適切な方法がない場合は、代わりに次のいずれかが当てはまることを確認する必要があります。
- 展開されたリストの最初のリンクは、ボタンの次のフォーカス順です(前のコード例のように)。
- 最初のリンクは、プログラムでリストを公開することに焦点を当てています。
この場合、(1)をお勧めします。 フォーカスをボタンに戻すことや、どのイベントで行うかを心配する必要がないため、はるかに簡単です。 また、現在、ユーザーの焦点が別の場所に移動することをユーザーに警告するものはありません。 すぐに説明する本当のメニューでは、これはaria-haspopup="true"
の仕事です。
aria-controls
使用しても、スクリーンリーダーでの読み取りがより冗長になることを除いて、それほど害はありません。 ただし、一部のJAWSユーザーはそれを期待する場合があります。 リストのid
を暗号として使用して、次のように適用されます。
<nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>
メニューとmenuitemの役割
真のメニュー(WAI-ARIAの意味で)は、 menu
ロール(コンテナ用)と、通常はmenuitem
の子(他の子ロールが適用される場合があります)を使用して、それ自体を識別する必要があります。 これらの親と子の役割は連携して、支援技術に情報を提供します。 メニューセマンティクスを持つようにリストを拡張する方法は次のとおりです。
<ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>
私たちのナビゲーションメニューは「本当の」メニューのように振る舞い始めているので、これらは存在すべきではありませんか?
簡単な答えは:いいえ。 長い答えは次のとおりです。いいえ。リストアイテムにはリンクが含まれており、 menuitem
要素はインタラクティブな子孫を持つことを目的としていないためです。 つまり、これらはメニューのコントロールです。
もちろん、 role="presentation"
またはrole="none"
(同等)を使用して<li>
のリストセマンティクスを抑制し、各リンクにmenuitem
ロールを配置することもできます。 ただし、これにより暗黙的なリンクの役割が抑制されます。 つまり、次の例は、「ホーム、リンク」または「ホーム、メニュー項目、リンク」ではなく、「ホーム、メニュー項目」としてアナウンスされます。 ARIAロールは単にHTMLロールをオーバーライドします。
<!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>
リンクを使用していて、リンクの動作を期待できることをユーザーに知らせてほしいので、これは良くありません。 私が言ったように、本当のメニューは(JavaScript駆動の)アプリケーションの振る舞いのためのものです。
残っているのは一種のハイブリッドコンポーネントです。これは実際のメニューではありませんが、 aria-expanded
状態のおかげで、少なくともリンクのリストが開いているかどうかをユーザーに通知します。 これは、ナビゲーションメニューにとって完全に満足のいくパターンです。
補足: <select>
要素
レスポンシブデザインに最初から関わっていた場合、ナビゲーションが狭いビューポートの<select>
要素に凝縮されたパターンを覚えているかもしれません。
ここで説明したチェックボックスベースのトグルボタンと同様に、スクリプトを追加せずに意図したとおりに動作するネイティブ要素を使用することは、効率と、特にモバイルでのパフォーマンスにとって良い選択です。 また、 <select>
要素は一種のメニューであり、まもなく作成するボタントリガーメニューと同様のセマンティクスを備えています。
ただし、チェックボックスのトグルボタンと同様に、単に選択を行うのではなく、入力の入力に関連する要素を使用しています。 これは、多くのユーザーに混乱を引き起こす可能性があります。特に、このパターンではJavaScriptを使用して、選択した<option>
をリンクのように動作させるためです。 これが誘発するコンテキストの予期しない変更は、WCAGの3.2.2入力時(レベルA)基準に従って失敗と見なされます。
真のメニュー
偽のメニューと準メニューについて説明したので、真のメニューボタンで開閉する真のメニューを作成する時が来ました。 これ以降、ボタンとメニューをまとめて単に「メニューボタン」と呼びます。
しかし、どのような点で私たちのメニューボタンは本当ですか? それは、対象のアプリケーションでオプションを選択することを目的としたメニューコンポーネントであり、そのようなツールで従来と見なされるすべての期待されるセマンティクスと対応する動作を実装します。
すでに述べたように、これらの規則はデスクトップアプリケーションの設計に由来します。 それらを完全に模倣するには、ARIAアトリビューションとJavaScriptで管理されるフォーカス管理が必要です。 ARIAの目的の一部は、Web開発者が、ネイティブワールドで作成されたユーザビリティの慣習に違反することなく、リッチなWebエクスペリエンスを作成できるようにすることです。
この例では、アプリケーションがある種のゲームまたはクイズであると想像します。 メニューボタンを使用すると、ユーザーは難易度を選択できます。 すべてのセマンティクスが整っていると、メニューは次のようになります。
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>
ノート
aria-haspopup
プロパティは、ボタンがメニューを秘密にすることを単に示します。 これは、押すと、ユーザーが「ポップアップ」メニューに移動することを警告する役割を果たします(フォーカスの動作については後ほど説明します)。 その値は変わりません—常にtrue
ままです。- ボタン内の
<span>
には、黒い下向きの小さな三角形のUnicodeポイントが含まれています。 この規則は、aria-haspopup
が非視覚的に行うことを視覚的に示します。つまり、ボタンを押すと、その下に何かが表示されます。aria-hidden="true"
属性は、スクリーンリーダーが「下向きの三角形」などをアナウンスするのを防ぎます。aria-haspopup
おかげで、非ビジュアルコンテキストでは必要ありません。 -
aria-haspopup
プロパティは、aria-expanded
によって補完されます。 これは、true
とfalse
値を切り替えることにより、メニューが現在開いている(展開されている)状態か閉じている(折りたたまれている)状態かをユーザーに通知します。 - メニュー自体が(適切な名前の)
menu
の役割を果たします。menuitem
ロールを持つ子孫を取ります。 これらはmenu
要素の直接の子である必要はありませんが、この場合は簡単にするためです。
キーボードとフォーカスの動作
インタラクティブコントロールをキーボードでアクセスできるようにする場合、できる最善のことは適切な要素を使用することです。 ここでは<button>
要素を使用しているため、HTMLButtonElementインターフェイスで指定されているように、 EnterキーとSpaceキーのストロークでクリックイベントが発生することが保証されます。 また、ボタンに関連付けられたdisabled
プロパティを使用してメニュー項目を無効にできることも意味します。
ただし、メニューボタンのキーボード操作にはまだまだたくさんのことがあります。 これは、WAI-ARIAオーサリングプラクティス1.1に基づいて、実装するすべてのフォーカスとキーボードの動作の概要です。
メニューボタンに「」、「スペース」、または「 ↓ 」と入力します | メニューを開きます |
メニュー項目の↓ | フォーカスを次のメニュー項目に移動します。最後のメニュー項目にいる場合は最初のメニュー項目に移動します |
メニュー項目の↑ | 前のメニュー項目、または最初のメニュー項目にいる場合は最後のメニュー項目にフォーカスを移動します |
メニューボタンの↑ | 開いている場合はメニューを閉じます |
メニュー項目のEsc | メニューを閉じて、メニューボタンをフォーカスします |
矢印キーを使用してメニュー項目間でフォーカスを移動する利点は、メニューから移動するためにタブが保持されることです。 実際には、これは、ユーザーがメニューを終了するためにすべてのメニュー項目を移動する必要がないことを意味します。これは、特に多くのメニュー項目がある場合に、使いやすさを大幅に向上させます。
tabindex="-1"
を適用すると、メニュー項目はTabでフォーカスできなくなりますが、矢印キーでキーストロークをキャプチャすると、プログラムで要素のフォーカスを合わせることができます。
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>
オープンメソッド
健全なAPI設計の一部として、さまざまなイベントを処理するためのメソッドを構築できます。
たとえば、 open
メソッドは、 aria-expanded
値を「true」に切り替え、メニューの非表示プロパティをfalse
に変更し、無効になっていないメニューの最初のmenuitem
をフォーカスする必要があります。
MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }
ユーザーがフォーカスされたメニューボタンインスタンスで下キーを押すと、このメソッドを実行できます。
this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));
さらに、このスクリプトを使用する開発者は、プログラムでメニューを開くことができるようになります。
exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();
補足:チェックボックスハック
可能な限り、必要がない限りJavaScriptを使用しないことをお勧めします。 HTMLとCSSに加えて3番目のテクノロジーを含めることは、必然的にシステムの複雑さと脆弱性の増加です。 ただし、JavaScriptを組み合わせなくても、すべてのコンポーネントを十分に構築できるわけではありません。
メニューボタンの場合、「JavaScriptなしで機能する」ようにする熱意が、チェックボックスハックと呼ばれるものにつながっています。 これは、非表示のチェックボックスのチェックされた(またはチェックされていない)状態を使用して、CSSを使用してメニュー要素の表示を切り替える場所です。
/* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }
スクリーンリーダーのユーザーにとって、チェックボックスの役割とチェックされた状態は、このコンテキストでは無意味です。 これは、チェックボックスにrole="button"
を追加することで部分的に克服できます。
<input type="checkbox" role="button" aria-haspopup="true">
残念ながら、これにより暗黙的なチェック状態の通信が抑制され、JavaScriptを使用しない状態フィードバックが失われます(このコンテキストでは「チェック」されているとは言えませんが)。
ただし、 aria-expanded
をスプーフィングすることは可能です。 以下のように、ラベルに2つのスパンを指定する必要があります。
<input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded</span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">▾</span> </label>
これらは両方とも、 visually-hidden
になっていますが、現在の状態に応じて、スクリーンリーダーにも非表示になっているのは1つだけです。 つまり、1つだけがdisplay: none
、これは現存する(ただし通信されていない)チェック状態によって決定されます:
/* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }
これは賢明ですべてですが、これまで説明してきた予想されるフォーカス動作はJavaScriptなしでは実装できないため、メニューボタンはまだ不完全です。
これらの動作は従来のものであり、予想されるものであり、ボタンをより使いやすくします。 ただし、JavaScriptを使用せずにメニューボタンを実装する必要がある場合は、これは可能な限り近いものです。 以前に取り上げたカットダウンナビゲーションメニューボタンがJavaScript自体に依存しないメニューコンテンツ(つまりリンク)を提供することを考えると、このアプローチは適切なオプションかもしれません。
楽しみのために、JavaScriptを使用しないナビゲーションメニューボタンを実装するcodePenを次に示します。
CodePenのHeydon(@heydon)によるJSなしのペンナビゲーションメニューボタンの例を参照してください。
(注:メニューを開くのはスペースのみです。)
「選択」イベント
一部のメソッドを実行すると、リスナーを設定できるようにイベントが発行されます。 たとえば、ユーザーがメニュー項目をクリックしたときに、 choose
イベントを発行できます。 これは、 CustomEvent
を使用して設定できます。これにより、イベントのdetail
プロパティに引数を渡すことができます。 この場合、引数(「選択」)は、選択されたメニュー項目のDOMノードになります。
MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }
このメカニズムでできることはいろいろあります。 おそらく、 menuFeedback
のid
で設定されたライブリージョンがあります。
<div role="alert"></div>
これで、リスナーを設定し、イベント内で秘密にされた情報をライブリージョンに入力できます。
exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
メニュー項目を選択すると、スクリーンリーダーのユーザーに「[メニュー項目のラベル]を選択しました」というメッセージが表示されます。 ライブリージョン(ここではrole=“alert”
属性で定義)は、コンテンツが変更されるたびにスクリーンリーダーでそのコンテンツをアナウンスします。 ライブリージョンは必須ではありませんが、ユーザーがメニューを選択したときの応答としてインターフェイスで発生する可能性のある例です。
永続的な選択肢
すべてのメニュー項目が永続的な設定を選択するためのものではありません。 多くは、押されたときにインターフェイスで何かを発生させる標準のボタンのように機能します。 ただし、難易度メニューボタンの場合は、現在の難易度設定(最後に選択したもの)を示します。
aria-checked="true"
属性は、 menuitemradio
menuitem
役割を担うアイテムに対して機能します。 2番目の項目がチェック(設定)された拡張マークアップは、次のようになります。
<button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">▾</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>
多くのプラットフォームのネイティブメニューは、チェックマークを使用して選択されたアイテムを示します。 少し余分なCSSを使用すれば、問題なくそれを行うことができます。
[role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }
スクリーンリーダーを実行したままメニューを移動しているときに、このチェック項目にフォーカスを合わせると、「チェックマーク、中メニュー項目、チェック済み」などのアナウンスが表示されます。
menuitemradio
をチェックしてメニューを開くときの動作は少し異なります。 メニューの最初の(有効な)項目に焦点を合わせる代わりに、チェックされた項目に焦点を合わせます。
この動作の利点は何ですか? ユーザー(任意のユーザー)は、以前に選択したオプションを思い出します。 多数の増分オプション(たとえば、ズームレベルのセット)を備えたメニューでは、キーボードで操作する人は、調整を行うために最適な位置に配置されます。
スクリーンリーダーでメニューボタンを使用する
このビデオでは、VoiceoverスクリーンリーダーとChromeでメニューボタンを使用する方法を紹介します。 この例では、 menuitemradio
、 aria-checked
、および説明されているフォーカス動作を備えたアイテムを使用しています。 同様のエクスペリエンスは、人気のあるスクリーンリーダーソフトウェアの全範囲で期待できます。
Githubの包括的なメニューボタン
Kitty Giraudelと私は、説明したAPI機能などを使用してメニューボタンコンポーネントを作成するために協力してきました。 これらの機能の多くは、アクセス可能なモーダルダイアログであるa11y-dialogで行った作業に基づいているため、Hugoに感謝する必要があります。 GithubとNPMで利用できます。
npm i inclusive-menu-button --save
さらに、キティはあなたの喜びのためにReactバージョンを作成しました。
チェックリスト
- ナビゲーションメニューシステムでARIAメニューセマンティクスを使用しないでください。
- コンテンツの多いサイトでは、ネストされたドロップダウンを利用したナビゲーションメニューで構造を隠さないでください。
-
aria-expanded
を使用して、ボタンでアクティブ化されたナビゲーションメニューの開閉状態を示します。 - ナビゲーションメニューが開閉するボタンの次にフォーカス順になっていることを確認してください。
- JavaScriptを使用しないソリューションを追求する際に、使いやすさを決して犠牲にしないでください。 それは虚栄心です。