Angular CDK を使ってアクセシブルなコンポーネントを作ろう

6 min read読了の目安(約6200字

はじめに

この記事は Angular Advent Calendar 2020 の12日目の記事です。

2020年を振り返ると、個人的にアクセシビリティへの関心が高まった年でした。
というわけで、今日はアクセシビリティ関連の話をしたいと思います。

Angularとアクセシビリティ

Angularアプリケーションでアクセシビリティを気にしていく際は、まず公式ドキュメントが手がかりになると思います。

https://angular.jp/guide/accessibility

私が個人的にまとめた資料も参考までに貼っておきます。

https://speakerdeck.com/shira/shi-jian-akusesibiritei-in-angularapurikesiyon

ドキュメントでも触れられていますが、Angular CDK には Accessibility モジュールがあります。うまく利用すればアクセシブルなコンポーネントを素早く実装することができそうです。

Angular CDK Accessibility モジュールのドキュメントはこちらです。

https://material.angular.io/cdk/a11y/overview

ドキュメントがあることは大変ありがたいです。
しかし正直なところ、サンプルコードが少なくぱっと見使い方がよくわからないと感じました。

そこでこの記事では、特に使えそうだなと思った FocusTrapListKeyManager について紹介したいと思います。

FocusTrapListKeyManager はよく独自実装するであろう ダイアログリストボックス で使えそうなものになっています。ユースケースから使用方法を説明していきたいと思います。

ダイアログ

ダイアログの実装どうしてますか?

ダイアログはよく見かけるUIです。dialogというタグがありますが、現状使えないブラウザがあることもあり、使っている人はきっと少ないだろうと思います。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog

ダイアログとアクセシビリティ

標準のタグを使わずに実装する場合、アクセシビリティのことを気にし出すと、とてもとても大変だと思います。ドキュメントをしっかり読んで、理解して、実装して、動作を確認して...大変です。『標準で用意してくださいませんか、頼む頼む〜(>_<)』 という気持ちになります。

ちなみにダイアログで気をつけることは以下ドキュメントを参照ください。
(日本語ドキュメントがありました。素晴らしい...ありがとうありがとう...)

https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal
https://waic.jp/docs/2019/NOTE-wai-aria-practices-1.1-20190207/#dialog_modal

キーボード・インタラクションに関してだけ書き出してみます。

  • キーボード・インタラクション
    • ダイアログが開くとき、フォーカスはダイアログの中の要素に移動します。初期状態のフォーカスの位置に関しては、以下の注意を見てください。
    • タブ:
      • ダイアログ内の次のタブ可能要素にフォーカスを移動します。
      • もし、フォーカスが、ダイアログの中の最後のタブ可能要素にあるなら、ダイアログの中の最初のタブ可能要素にフォーカスを移動します。
    • シフト + タブ:
      • ダイアログ内の前のタブ可能要素にフォーカスを移動します。
      • もし、フォーカスが、ダイアログの中の最初のタブ可能要素にあるなら、ダイアログの中の最後のタブ可能要素にフォーカスを移動します。
    • エスケープ: ダイアログを閉じます。

キーボード・インタラクションに関してだけでもいくつか気にすることがあります。
これを自分たちで実装することは可能ですが、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 {}

Step3. ダイアログの枠にあたる要素に cdkTrapFocus ディレクティブを追加

<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からご確認ください。

https://gqairbagj.github.stackblitz.io/dialog-sample

FocusTrapでできないこと

以下はできないようなので自分で実装する必要がありそうです。
FocusTrapはダイアログ専用ではないだろうから、そりゃそうか、という感じではありました。

  • ダイアログが開くとき、フォーカスをダイアログに移動する
  • エスケープキー押下時にダイアログを閉じる
  • WAI-ARIAのRole、States、Propertyの付与

リストボックス

リストボックスの実装どうしてますか?

リストボックスは<select><option>タグを使って実装することができます。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/select
https://developer.mozilla.org/ja/docs/Web/HTML/Element/option

じゃあいいやん。めでたし。となりそうですが、ちょっと課題があります。

昨今のWebサイト、Webアプリではブラウザ標準のスタイルのまま使用しないケースが大半でしょう。<select><option>に限らず、<input><button>などサイトやアプリケーションに合わせてスタイルを当てるのが一般的です。

ただ、<select><option>に関してはそれが難しいです。現状、<select>のスタイルは変更できますが、<option>のスタイルを変更することができません。その場合、想定される選択肢は以下のようなものでしょう。

  1. <select>だけスタイルを変えて<option>はブラウザデフォルトのままとする
  2. <select><option>を使わずに実装し、任意のスタイルを適用する
  3. 何かしらのUIコンポーネントを採用し、場合によってはスタイルを調整する

リストボックスとアクセシビリティ

一度アクセシビリティのことを気にし出すと、独自実装する際には無視できなくなってくると思います。仮に独自実装する場合、どんなことに気をつければ良いのでしょうか?

リストボックスの仕様書はこちらです。

https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal
https://waic.jp/docs/2019/NOTE-wai-aria-practices-1.1-20190207/#Listbox

すごくいろいろ書かれておりますが、これと向き合って実装していく人を尊敬します。。すごい。
独自実装以外の方針に激しく心が揺れてしまいます。

そこで ListKeyManager の出番です。シンプルな仕様のリストボックスであれば、 ListKeyManager で実装がだいぶ楽になりそうです。

ListKeyManagerでできること

  • キーボードの上下キーでリストのアイテム間を移動することができます
  • ActiveDescendantKeyManager とオプションを組み合わせることで、アイテムのスキップや絞り込みなどできるようです。すごい!(こちらはサンプル未実装なので、今後追加したいと思います。)

(他にもできることがありそうですが、検証が間に合っていません。ごめんなさい。今後頑張れたら追記します。)

ListKeyManagerの使い方

キーマネージャーの種類は FocusKeyManagerActiveDescendantKeyManager の2種類です。

  • FocusKeyManager
    • オプションがブラウザのフォーカスを直接受け取る場合に使用されます。管理される各アイテムは、FocusableOption インターフェイスを実装する必要があります。
  • ActiveDescendantKeyManager
    • オプションが aria-activedescendant を介してアクティブとしてマークされる場合に使用されます。管理される各アイテムは、Highlightable を実装する必要があります。

サンプル実装では以下の記事を参考にさせていただきました。現状私が作ったサンプルは FocusKeyManagerを使用したもののみです。ActiveDescendantKeyManager を使うとより複雑なリストボックスを簡単に作成できそうです。今はないですが、今後サンプルに追加したいと思います。
使い方の説明は以下の記事とサンプルコードをみていただければ十分わかると思うので割愛します。

https://netbasal.com/accessibility-made-easy-with-angular-cdk-1caaf3d98de2

ListKeyManagerを使ったサンプル

以下URLからサンプルを確認できます。実際に使うには色々足りていませんが、上下キーでの操作に関しては問題なく動いています。(現状、上記で紹介した記事のサンプルの方が参考になると思いますので上記記事のサンプルを見れば十分ですm(_ _)m)

https://gqairbagj.github.stackblitz.io/list-sample

ListKeyManagerでできないこと

WAI-ARIAに関しては、FocusTrap同様、リストボックス専用ではないためそりゃそうかという感じですね。

  • WAI-ARIAのRole、States、Propertyの付与

サンプル

本記事で紹介したサンプルは GitHub と StackBlitz 確認できます。
数時間ざっくり検証したレベルなので、基本的な使い方のみの紹介となりますが、実際に使えそうだなということはわかりました。

https://stackblitz.com/github/k-shirahama/angular-material-a11y-sample
https://github.com/k-shirahama/angular-material-a11y-sample

まとめ

実は、ダイアログもリストボックスも実際に携わっているプロダクトで課題に挙がった内容でした。プロジェクト開始時にはこの記事で紹介した機能を知らなかったので、独自実装をしたり、妥協案を検討するなどしたのですが、プロジェクト開始前から知っていたら積極的に採用したでしょう。

ダイアログとリストボックス、どちらも独自で実装するケースが割と多いのではないかと思います。是非Angular CDK の利用を検討してみてはいかがでしょうか。

Angular Advent Calendar 2020 6日目の @fusho-takahashiさんの記事で紹介されてましたが、今後の開発が進んでいくことも期待できそうなので楽しみです。

https://qiita.com/fusho-takahashi/items/eb7634ccbd19ed959e9c#実はまだ奥がある-未来のcdk

自分自身まだ把握できてない機能がたくさんあるので、Angular CDK、Angular Material をもっと活用していけるよう、キャッチアップしていきたいと思います。