Angular CDK を使ってアクセシブルなコンポーネントを作ろう
はじめに
この記事は Angular Advent Calendar 2020 の12日目の記事です。
2020年を振り返ると、個人的にアクセシビリティへの関心が高まった年でした。
というわけで、今日はアクセシビリティ関連の話をしたいと思います。
Angularとアクセシビリティ
Angularアプリケーションでアクセシビリティを気にしていく際は、まず公式ドキュメントが手がかりになると思います。
私が個人的にまとめた資料も参考までに貼っておきます。
ドキュメントでも触れられていますが、Angular CDK には Accessibility モジュールがあります。うまく利用すればアクセシブルなコンポーネントを素早く実装することができそうです。
Angular CDK Accessibility モジュールのドキュメントはこちらです。
ドキュメントがあることは大変ありがたいです。
しかし正直なところ、サンプルコードが少なくぱっと見使い方がよくわからないと感じました。
そこでこの記事では、特に使えそうだなと思った FocusTrap
と ListKeyManager
について紹介したいと思います。
FocusTrap
と ListKeyManager
はよく独自実装するであろう ダイアログ
や リストボックス
で使えそうなものになっています。ユースケースから使用方法を説明していきたいと思います。
ダイアログ
ダイアログの実装どうしてますか?
ダイアログはよく見かけるUIです。dialog
というタグがありますが、現状使えないブラウザがあることもあり、使っている人はきっと少ないだろうと思います。
ダイアログとアクセシビリティ
標準のタグを使わずに実装する場合、アクセシビリティのことを気にし出すと、とてもとても大変だと思います。ドキュメントをしっかり読んで、理解して、実装して、動作を確認して...大変です。『標準で用意してくださいませんか、頼む頼む〜(>_<)』 という気持ちになります。
ちなみにダイアログで気をつけることは以下ドキュメントを参照ください。
(日本語ドキュメントがありました。素晴らしい...ありがとうありがとう...)
キーボード・インタラクションに関してだけ書き出してみます。
- キーボード・インタラクション
- ダイアログが開くとき、フォーカスはダイアログの中の要素に移動します。初期状態のフォーカスの位置に関しては、以下の注意を見てください。
- タブ:
- ダイアログ内の次のタブ可能要素にフォーカスを移動します。
- もし、フォーカスが、ダイアログの中の最後のタブ可能要素にあるなら、ダイアログの中の最初のタブ可能要素にフォーカスを移動します。
- シフト + タブ:
- ダイアログ内の前のタブ可能要素にフォーカスを移動します。
- もし、フォーカスが、ダイアログの中の最初のタブ可能要素にあるなら、ダイアログの中の最後のタブ可能要素にフォーカスを移動します。
- エスケープ: ダイアログを閉じます。
キーボード・インタラクションに関してだけでもいくつか気にすることがあります。
これを自分たちで実装することは可能ですが、Angular CDK の FocusTrap
を使うといくらか楽に実装できるようになります。
FocusTrapでできること
ダイアログに求められる タブ
、シフト + タブ
キー押下時の挙動が簡単に実装でます。
FocusTrapの使い方
Step1. Angular のプロジェクトに Angular CDK を追加
ng add @angular/cdk
Step2. A11yModuleをインポート(app.module.tsに追加)
import ...(省略)
@NgModule({
declarations: [ ...(省略)],
imports: [
...(省略)
A11yModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
cdkTrapFocus
ディレクティブを追加
Step3. ダイアログの枠にあたる要素に <section>
<h2>sample1</h2>
<div class="Sample1_Content" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
<ul>
<li><a routerLink="/">cdkfocusregionstartの外</a></li>
<li><a routerLink="/" cdkfocusregionstart="true">cdkfocusregionstart</a></li>
<li><a routerLink="/" cdkFocusInitial>cdkFocusInitial</a></li>
<li><a routerLink="/">何もなし</a></li>
<li><a routerLink="/" cdkfocusregionend="true">cdkfocusregionend</a></li>
<li><a routerLink="/">cdkfocusregionendの外</a></li>
</ul>
</div>
</section>
基本、これだけで動きます。お手軽です。自分で実装するより断然早いでしょう。
FocusTrapを使ったサンプル
実際のサンプルやオプションの挙動が気になる方は以下URLからご確認ください。
FocusTrapでできないこと
以下はできないようなので自分で実装する必要がありそうです。
FocusTrapはダイアログ専用ではないだろうから、そりゃそうか、という感じではありました。
- ダイアログが開くとき、フォーカスをダイアログに移動する
- エスケープキー押下時にダイアログを閉じる
- WAI-ARIAのRole、States、Propertyの付与
リストボックス
リストボックスの実装どうしてますか?
リストボックスは<select>
と<option>
タグを使って実装することができます。
じゃあいいやん。めでたし。となりそうですが、ちょっと課題があります。
昨今のWebサイト、Webアプリではブラウザ標準のスタイルのまま使用しないケースが大半でしょう。<select>
と<option>
に限らず、<input>
や<button>
などサイトやアプリケーションに合わせてスタイルを当てるのが一般的です。
ただ、<select>
と<option>
に関してはそれが難しいです。現状、<select>
のスタイルは変更できますが、<option>
のスタイルを変更することができません。その場合、想定される選択肢は以下のようなものでしょう。
-
<select>
だけスタイルを変えて<option>
はブラウザデフォルトのままとする -
<select>
と<option>
を使わずに実装し、任意のスタイルを適用する - 何かしらのUIコンポーネントを採用し、場合によってはスタイルを調整する
リストボックスとアクセシビリティ
一度アクセシビリティのことを気にし出すと、独自実装する際には無視できなくなってくると思います。仮に独自実装する場合、どんなことに気をつければ良いのでしょうか?
リストボックスの仕様書はこちらです。
すごくいろいろ書かれておりますが、これと向き合って実装していく人を尊敬します。。すごい。
独自実装以外の方針に激しく心が揺れてしまいます。
そこで ListKeyManager
の出番です。シンプルな仕様のリストボックスであれば、 ListKeyManager
で実装がだいぶ楽になりそうです。
ListKeyManagerでできること
- キーボードの上下キーでリストのアイテム間を移動することができます
-
ActiveDescendantKeyManager
とオプションを組み合わせることで、アイテムのスキップや絞り込みなどできるようです。すごい!(こちらはサンプル未実装なので、今後追加したいと思います。)
(他にもできることがありそうですが、検証が間に合っていません。ごめんなさい。今後頑張れたら追記します。)
ListKeyManagerの使い方
キーマネージャーの種類は FocusKeyManager
と ActiveDescendantKeyManager
の2種類です。
- FocusKeyManager
- オプションがブラウザのフォーカスを直接受け取る場合に使用されます。管理される各アイテムは、
FocusableOption
インターフェイスを実装する必要があります。
- オプションがブラウザのフォーカスを直接受け取る場合に使用されます。管理される各アイテムは、
- ActiveDescendantKeyManager
- オプションが
aria-activedescendant
を介してアクティブとしてマークされる場合に使用されます。管理される各アイテムは、Highlightable
を実装する必要があります。
- オプションが
サンプル実装では以下の記事を参考にさせていただきました。現状私が作ったサンプルは FocusKeyManager
を使用したもののみです。ActiveDescendantKeyManager
を使うとより複雑なリストボックスを簡単に作成できそうです。今はないですが、今後サンプルに追加したいと思います。
使い方の説明は以下の記事とサンプルコードをみていただければ十分わかると思うので割愛します。
ListKeyManagerを使ったサンプル
以下URLからサンプルを確認できます。実際に使うには色々足りていませんが、上下キーでの操作に関しては問題なく動いています。(現状、上記で紹介した記事のサンプルの方が参考になると思いますので上記記事のサンプルを見れば十分ですm(_ _)m)
ListKeyManagerでできないこと
WAI-ARIAに関しては、FocusTrap同様、リストボックス専用ではないためそりゃそうかという感じですね。
- WAI-ARIAのRole、States、Propertyの付与
サンプル
本記事で紹介したサンプルは GitHub と StackBlitz 確認できます。
数時間ざっくり検証したレベルなので、基本的な使い方のみの紹介となりますが、実際に使えそうだなということはわかりました。
まとめ
実は、ダイアログもリストボックスも実際に携わっているプロダクトで課題に挙がった内容でした。プロジェクト開始時にはこの記事で紹介した機能を知らなかったので、独自実装をしたり、妥協案を検討するなどしたのですが、プロジェクト開始前から知っていたら積極的に採用したでしょう。
ダイアログとリストボックス、どちらも独自で実装するケースが割と多いのではないかと思います。是非Angular CDK の利用を検討してみてはいかがでしょうか。
Angular Advent Calendar 2020 6日目の @fusho-takahashiさんの記事で紹介されてましたが、今後の開発が進んでいくことも期待できそうなので楽しみです。
自分自身まだ把握できてない機能がたくさんあるので、Angular CDK、Angular Material をもっと活用していけるよう、キャッチアップしていきたいと思います。
Discussion