HTMLのマークアップでアクセシビリティを向上させる方法として、WAI-ARIA(ウェイ・アリア)があります。HTML要素の属性に aria-* を追加することで音声読み上げの精度を高められます。

WAI-ARIAの特徴として、状態を示す属性が存在します。例えば、aria-selected 属性。名前の通り、該当のHTML要素が選択状態であるかを示すことができます。ウェブサイトのUIパーツにはタブやアコーディオンなど動的なものがあります。こういった動的なパーツの「状態」はHTMLのマークアップだけでは示すことが難しく、JavaScriptの実装が不可欠です。

180109_angular_dom.gif

▲本記事で解説するサンプル。タブの切り替えによって、aria-selected属性を動的に切り替えている。JavaScriptの制御が必要である。

よく利用される例としてタブのユーザーインターフェースを題材とし、流行りのJSライブラリのAngular 5の実装方法を紹介します。

WAI-ARIAのサンプルの読み上げ

まずは、WAI-ARIA対応有無によってどのような違いがあるのか確認しましょう。

macOS標準搭載のVoiceOverを使うことで、ウェブページを読み上げられます。特別なブラウザは必要なく、ChromeやSafariで読み上げを確認できます。解説のために半透明の黒い吹き出しのなかに読み上げられた音声の文言を表示させています(実際には音声が流れています)。

アクセシビリティに対応してコーディングできていれば、しっかりと読み上げられています。タブキーで操作可能であり、選択されているものが「tab」であることやタブの順番(例:1/3)、選択中のタブは「選択された項目」と状態が認識されています。

180109_angular_voiceover.gif

対して、全くアクセシビリティを意識せずに設計した場合どうなるでしょう? 次のサンプルは、aria属性やrole属性を使わず、さらにa要素やbutton要素、tabindex属性など全く配慮しなかったものです。ほとんど的外れな読み上げをしていて使い物になりません。タブキーで操作不能でマウスでしか操作できません。

180109_react_bad_sample.gif

Angularで実装したWAI-ARIAのサンプル

では、WAI-ARIAに対応したAngularのサンプルを紹介します。GitHubにアップしているので、デモとソースコードをご覧ください。

前提として、Angular CLIAngular 5)で環境構築したものとします。Angular 2以上であれば、本記事の内容が使えると思います。なお、AngularJS 1.x系とは互換性がないので注意ください。

Angularでのステート管理

選択されたタブのIDをプロパティtabに保持することとします。

app.component.ts
export class AppComponent {
  tab = 'panel1';

  // (一部省略)
}

※コードは抜粋で掲載しているので、コピーする際はGitHubの「app.component.ts」を参照ください。

HTMLの実装

HTMLの実装を紹介します。

app.component.html
<div>
  <ul role="tablist">
    <li role="presentation">
      <button role="tab"
              aria-controls="panel1"
              [attr.aria-selected]="tab === 'panel1'"
              (click)="_handleClick($event)">
        カベルネ・ソーヴィニョン
      </button>
    </li>
    <li role="presentation">
      <button role="tab"
              aria-controls="panel2"
              [attr.aria-selected]="tab === 'panel2'"
              (click)="_handleClick($event)">
        メルロー
      </button>
    </li>
    <li role="presentation">
      <button role="tab"
              aria-controls="panel3"
              [attr.aria-selected]="tab === 'panel3'"
              (click)="_handleClick($event)">
        ピノ・ノワール
      </button>
    </li>
  </ul>
  <div role="tabpanel"
       id="panel1"
       [attr.aria-hidden]="tab !== 'panel1'">
    カベルネ・ソーヴィニョンはブドウの一品種。赤ワインの中でも渋くて重い味わいが特徴です。
  </div>
  <div role="tabpanel"
       id="panel2"
       [attr.aria-hidden]="tab !== 'panel2'">
    メルローはブドウの一品種。味はカベルネ・ソーヴィニョンほど酸味やタンニンは強くなく、芳醇でまろやかで繊細な味わいです。
  </div>
  <div role="tabpanel"
       id="panel3"
       [attr.aria-hidden]="tab !== 'panel3'">
    ピノ・ノワールはブドウの一品種。カベルネ・ソーヴィニョンと対照的で比較的軽口な味わいです。
  </div>
</div>

※コードは抜粋で掲載しているので、コピーする際はGitHubの「app.component.html」を参照ください。

HTMLコーディングのポイントとしては次の通り。

  • 期待どおりに読み上げられるようにrole属性を適切に利用します
  • タブとして機能するように、ul要素にrole="tablist"、 タブ部分となるbutton要素にrole="tab"、 パネル部分のdiv要素にrole="tabpanel"を追加します
    • 慣習に従ってul>liでマークアップしましたが、読み上げの支障となるのでliタグにはrole="presentation"を指定してます(もしかしたらタブUIにul>liを使う必要はないかもしれません)

JavaScriptとAngularに絡んでくるポイントは次の通り。

  • タブとなるbutton要素とパネルのdiv要素の関連性を示すためaria-controls属性を指定します。値は任意でid属性で指定します
  • button要素にタブの選択状態を伝えるために、aria-selectedを真偽値で指定します
    • Angularのプロパティの値で動的とします。こうすれば半自動的にaria-selected属性が切り替わります
  • パネル部分が表示・非表示の状態を伝えるために aria-hidden属性を真偽値で指定します

JSの実装

ボタン要素のイベントハンドラーのコードを紹介します。ボタンとパネルの紐付けは、意味的に合致している aria-controls 属性を利用してます。JavaScriptの制御が必要なものは独自の変数ではなく、可能な限り aria-* 属性で代替するのがベターなやり方と思います。

app.component.ts
export class AppComponent {
  tab = 'panel1';

  /**
   * クリックしたときのイベントハンドラーです。
   * @param event イベントオブジェクトです。
   */
   _handleClick(event: MouseEvent): void {
    // イベント発生源の要素を取得
    const element = event.currentTarget as HTMLElement;

    // aria-controls 属性の値を取得
    const tabState = element.getAttribute('aria-controls');

    // プロパティを更新
    this.tab = tabState;
  }
}

※コードは抜粋で掲載しているので、コピーする際はGitHubの「app.component.ts」を参照ください。

CSSの実装

CSSはなるべく class 属性を使わず、aria-* 属性をセレクターとして指定しています。こうすれば、余計なクラス属性を増やす必要がなくなります。

app.component.css
/* タブパネルのUI制御のための指定 */
[aria-hidden="true"] {
    display: none;
}

[aria-hidden="false"] {
    display: block;
}

/* タブボタンのUI制御のための指定 */
[aria-selected="true"] {
    /* 選択されたときの色 */
    background-color: royalblue;
    color: white;
}

※コードは抜粋で掲載しているので、コピーする際はGitHubの「app.component.css」を参照ください。

CSSの実装はCodeGridの記事「WAI-ARIAを活用したフロントエンド実装」で紹介されている「aria属性をCSSセレクタとして利用する」「独自に名前を付けるくらいなら、意味的に合致するaria属性を利用して、アクセシビリティを確保しましょう」の提案をアイデアとしています。

まとめ

Angularで実装する場合はタブの状態はいずれかのプロパティで管理しているはずです。その値を間借りして aria-* 属性に適用すれば、簡単にアクセシビリティを向上できます。今回はタブ型UIで紹介しましたが、これは一例に過ぎません。さまざまなユーザーインターフェースに利用できるので応用くださいませ。

昨年の記事「脱jQueryのためにしたこと - Qiita」でも紹介したように、AngularやVue.js等のJSライブラリとWAI-ARIAの相性は抜群です。ほんの少しWAI-ARIAの理解が進めば、Angularユーザーの皆さんは簡単に利用できるでしょう。私個人としても多くのプロダクトで積極的にWAI-ARIAを実装しています(パブリックな事例としては「ICS MEDIA」、「Beautifl」など)。この記事によって、音声読み上げを求めているエンドユーザーへの配慮が少しでも進めばと考えています。

この方法はVue.jsやReactでも実装できるので、続編記事で紹介します(この記事とほとんど同じ内容になると思いますが)。ぜひご利用ください。

補足

記事を作成するにあたり複数のサンプルを用意して音声読み上げソフト(macOSの「VoiceOver」や「NVDA日本語版」)で比較検証しましたが、マークアップはこれがベストの方法とは限りません。

本記事の主題はWAI-ARIAのベストな使い方を説明することではありません。状態明示のWAI-ARIAの実装にAngularが役立つと紹介することです。そのため、装飾や属性をシンプルなものとしています。コンテンツとの相性もあるでしょうから、ウェブサイトの方針にあわせて改良してください。