フロントエンドエンジニアのWeb開発アクセシビリティ入門③｜WAI-ARIA

## 初めに
### 記事を書いた人について
2022年3月からフロントエンドエンジニアとして働き始めました。
2024年9月現在、約2年ほどフロントエンドエンジニアとしての実務経験を積んでいます。

### この記事について
上記の通り、約2年実務経験を積み、そろそろアクセシビリティについてしっかり学ばなければならないと感じました。
自分と同じようなフロントエンドエンジニアとしてアクセシビリティを学び始める方に向けて、自分が学んだことを以下の3つの記事に分けて公開します。
- [フロントエンドエンジニアのWeb開発アクセシビリティ入門①｜HTML](https://qiita.com/msuzuna/private/2b0114d4f76b62b95c7a)
- [フロントエンドエンジニアのWeb開発アクセシビリティ入門②｜CSS/JavaScript](https://qiita.com/msuzuna/items/5f5f33658d3a346e31fb)
- フロントエンドエンジニアのWeb開発アクセシビリティ入門③｜WAI-ARIA（この記事）

なお、この記事は主に以下のページを自分なりにわかりやすくした内容です。

https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics

## WAI-ARIAって何？
### 新しい問題
Webがより複雑で動的なものになるにつれ、アクセシビリティの新たな機能と新たな問題が発生しました。

#### 具体例①
ページを作成するために、`<nav>`や`<footer>`などの多くのセマンティックな要素が導入されました。これらのタグが利用できる以前は、`<div classs="nav">`のように`<div>`タグをIDやclassとともに利用していました。
上記のような`<div>`タグをIDやclassとともに利用する実装の仕方は、メインナビゲーションのようなページ内の機能をプログラムで識別する簡単な方法がないという問題がありました。

#### 具体例②
日付選択のための日付ピッカーや値選択のためのスライダーなどの複雑なコントロールを使い出したケースがあります。
```html
<input type="date"> <input type="range">
```
これらはブラウザによっては十分にサポートされておらず、スタイル付けするのも困難です。結果として、JavaScriptライブラリを利用して複数のネストされた`<div>`で複雑なコントロールを生成するなどの対応がなされました。
上記のような複数のネストされた`<div>`で実装する方法は、見た目上は動作するもののスクリーンリーダーからはそれらが何なのか全く理解できないという問題がありました。

### WAI-ARIAの導入
前章で提示したような、プログラムから識別できないという問題を解決するための手段としてWAI-ARIAが利用されています。
WAI-ARIAは要素に適用できる追加の意味論を提供する一連のHTML属性を定義しています。情報が欠けている場所でアクセシビリティを向上させることができます。

<dl>
    <dt>ロール（Role）</dt>
    <dd>
    <ul>
        <li>
        要素が何か、もしくは何をするのかを定義する
        </li>
        <li>
        多くの場合ランドマークロール（landmark role）を示す
        <ul>
            <li>
            主にHTML構造の意味付けを複製する
            </li>
            <li>
            <code>role="navigation"</code>（<code>&lt;nav&gt;</code>）
            </li>
            <li>
            <code>role="complementary"</code>（<code>&lt;aside&gt;</code>）
            </li>
        </ul>
        </li>
        <li>
        ページの構造を定義するロールもある
        <ul>
            <li>
            <code>role="banner"</code>
            </li>
            <li>
            <code>role="search"</code>
            </li>
            <li>
            <code>role="tablist"</code>
            </li>
            <li>
            <code>role="tabpanel"</code>
            </li>
        </ul>
        </li>
    </ul>
    </dd>
    <dt>プロパティ（Property）</dt>
    <dd>
    <ul>
        <li>要素の性質を定義する</li>
        <li>追加の位置や意味論を与えるために使用することができる</li>
        <li><code>aria-required="true"</code>
            <ul>
                <li>フォーム入力が有効となるために値を入力しなければならないことを定義する</li>
            </ul>
        </li>
        <li><code>aria-labelledby</code>
            <ul>
                <li>要素にIDを追加することで、複数の場合も含めてページ内の他のどの要素からでもラベルとして参照することを可能にする</li>
            <ul>
        </li>
    </ul>
    </dd>
    <dt>状態（State）</dt>
    <dd>
    <ul>
        <li>要素の現在の状態を定義する特別なプロパティ</li>
        <li><code>aria-disabled="true"</code>
            <ul>
                <li>フォーム入力が現在disabledであることをスクリーンリーダーを通して伝える</li>
            </ul>
        </li>
        <li>JavaScriptによってプログラムから変更される</li>
    </ul>
    </dd>
</dl>
WAI-ARIA属性の重要な点は、ブラウザのアクセシビリティAPIによって提供される情報を除いて、Webページに何の影響をも与えないという点です。WAI-ARIA属性はWebページの構造やDOMに影響を与えませんが、CSSの要素選択で利用することが可能です。

### WAI-ARIAはどこでサポートされているか
以下の理由から、WAI-ARIAのどの機能がサポートされているかを記述する決定的なリソースを見つけるのは困難です。
1. WAI-ARIAには大量の機能がある
1. 検討しなければならないOS、ブラウザ、スクリーンリーダーの組み合わせが大量にある

2点目は重要です。
スクリーンリーダーを利用するためには、オペレーティングシステム（OS）が所定のアクセシビリティAPIを持つブラウザーを動作させる必要があります。そのためにはスクリーンリーダーを動作させるために必要な情報を提供する必要があります。ほとんどの人気OSはスクリーンリーダーが動作する1つか2つの所定のブラウザをユーザに提供しています。

次に、ブラウザが利用したいARIAの機能に対応しているのか、APIを通してそれらを公開しているのかを気にする必要があります。また、スクリーンリーダーがそれらの情報を認識し、ユーザに有益なやり方で伝えているのか、という点も気にしなければいけません。

1. 一般的な機能は全てのブラウザでサポートされている
1. スクリーンリーダーのARIA対応状況はブラウザほどではないが、一般的なスクリーンリーダーはブラウザのサポート状況に近いものになってきている

今回は全てのWAI-ARIAの機能と詳細について網羅していません。最も重要なWAI-ARIAの機能について解説しています。

### いつWAI-ARIAを使うべきか
WAI-ARIAが有用となる場面は以下の４通りあります。
- 道しるべ/ランドマーク（Signpost/Landmark）
    - `role`属性の値は、HTML要素の意味論を再現するランドマークとして振る舞うことがある
    - `role`属性の値は、`search`、`tablist`、`tab`、`listbox`のようにHTMLの意味論の範囲外となる道しるべ（signpost）を異なる機能領域に提供することができる
- 動的なコンテンツの更新
    - スクリーンリーダーは絶えず更新されるコンテンツが得意ではない傾向がある
    - `aria-live`を使うことで、コンテンツが更新された場合にスクリーンリーダーのユーザに対してそれを伝えることができる
- キーボードのアクセシビリティ向上
    - キーボードのアクセシビリティを持つHTML以外の要素にインタラクションさせる場合、WAI-ARIAはフォーカスを得る手段を提供している
- 意味論的ではないコントロールのアクセシビリティ
    - ネストした`<div>`が複雑なUI機能を構成していたり、ネイティブコントロールがJSによって大きく強化/変更されている場合、アクセシビリティの提供は困難になる
    - `button`、`listbox`、`tablist`といったロールの組み合わせ、もしくは`aria-required`や`aria-pointset`などのプロパティにより機能の手がかりを提供することでARIAは足りないものを補うことができる

重要なことは、**WAI-ARIAは必要な場合のみ使用する**ことです。
スクリーンリーダーの利用ユーザが内容を理解するのに必要な意味論は、ネイティブのHTML機能通して提供されることが理想です。しかし、ネイティブのHTML機能での意味論の提供が困難となる場合があります。この場合、WAI-ARIAはアクセシビリティを向上させる上で価値のあるツールとなります。

## 実践的なWAI-ARIAの実装
### 道しるべ/ランドマーク (Signpost/Landmark)
WAI-ARIAは`role`属性を追加することで、必要に応じて付加的な意味論的価値を追加することができます。

検索フォームを例に考えてみましょう。
検索フォームは`<form>`タグだけではVoiceOverのランドマークメニューには列挙されません。
```html
<form>
  <!-- 検索フォーム -->
</form>
```
ランドマークメニューに検索フォームを列挙するためには、以下のように`role`属性を追加すると良いでしょう。
```html
<form role="search">
  <!-- 検索フォーム -->
</form>
```
検索フォームにはラベルが設定されない場合があります。そのような場合は`aria-label`属性を設定することでスクリーンリーダーによって読み上げられる説明ラベルを設定できます。
```html
<form role="search">
  <input type="search" aria-label="Search through site content" >
</form>
```

`role`属性と`aria-label`属性を設定することで以下の2点を改善できます。
- ページを閲覧したときと、ランドマークメニューで見た時の両方で、検索フォームがアイテムとして認識できる
- フォーム入力がハイライトされた時、`aria-label`に含まれているテキストが読み上げられる

このようにARIA属性を適切に付与することでHTMLのみの意味論以上の意味づけを実現することができます。

### 動的なコンテンツの更新
DOMに読み込まれたコンテンツはスクリーンリーダーを利用して簡単にアクセスすることができます。そのため、静的Webサイトは視覚障害者のアクセシビリティを確保しやすくなっています。

問題は、現行のWebサイトは静的Webサイトだけではないということです。
サーバから新しいコンテンツを取得してDOMを更新することでページの一部を更新するようなWebサイトもあります。このようにコンテンツが更新されることは**ライブリージョン**と呼ばれることがあります。

ページが更新された際に通知を行う便利な機構が`aria-live`プロパティです。このプロパティを要素に適用すると、スクリーンリーダーが更新されたコンテンツを読み上げてくれるようになります。読み上げる緊急性は以下の属性値によって変わります。
<dl>
  <dt><code>off</code></dt>
  <dd>既定値。更新は通知されるべきではない。</dd>
  <dt><code>polite</code></dt>
  <dd>更新はユーザが暇になったときのみ通知されるべきである。</dd>
  <dt><code>assertive</code></dt>
  <dd>更新は可能な限り早くユーザに通知されるべきである。</dd>
</dl>

```html
<section aria-live="polite">…</section>
```

加えて、テキストの更新された部分だけを読み上げるべきかどうかを考慮すべきです。`aria-automic`プロパティを追加することで更新された一部分だけではなく、要素全体のコンテンツを1つのまとまりとして読み上げることができます。

以下のように`aria-atomic="true"`属性を付与することで、更新された一部分だけでなく、要素全体のコンテンツを一つのまとまりとして読み上げることができます。

```html
<section aria-live="polite" aria-atomic="true">…</section>
```

### キーボードでのアクセシビリティの拡張
HTMLの重要な能力の1つにキーボードでのアクセシビリティが含まれています。キーボードからボタンやフォームコントロール、リンクにアクセスできます。一般的に、タブキーでコントロール間を移動したり、エンター/リターンキーでコントロールの選択や活性化をしたりすることができます。

しかし、意味論的でない要素や正しい用途でないフォーカス可能な要素を利用するコードを書かざるを得ない場合もあるでしょう。

フォーカスできないコードをフォーカス可能にするため、`tabindex`属性を利用できます。

- `tabindex="0"`
    - 通常タブキーでの移動対象とならない要素をタブ移動可能にする
    - この値は`tabindex`の値の中で最も便利である
- `tabindex="-1"`
    - 通常タブキーでの移動対象とならない要素をプログラム的にフォーカス可能にする
    - 例えば、JavaScriptを利用したり、リンクのターゲットしてフォーカスするケースがある

### 意味論的でないコントロールのアクセシビリティ
複数の`<div>`要素を入れ子にし、CSS/JavaScriptを利用して複雑なUI機能を構築した場合や、JavaScriptで本来のコントロールの機能を拡張/改変した場合はキーボードアクセシビリティが損なわれるだけでなく、スクリーンリーダーユーザが各機能を理解しづらくなってしまいます。
このような状況においてもARIAは意味論を補足する手助けができます。

#### フォーム検査とエラー警告
フォームを送信しようとした際にエラーメッセージを表示させます。
```html
<div class="errors" role="alert" aria-relevant="all">
  <ul></ul>
</div>
```
- `role="alert"`
    - 適用先の要素を自動的にライブリージョンにする
        - その要素に対する変更が読み上げられる
    - その要素が警告メッセージであることを意味論的に特定している
- `aria-relevant="all"`
    - エラーが追加または削除された際にエラーリストの中身を読み上げることをスクリーンリーダーに命令するものである
    - ユーザが何のエラーが残っているのか知りたいため有用だ

フォームを作成するにあたって、そもそもフィールドが必須かどうかを示すことや、年齢がどの範囲にあるべきかを示すことなどを考慮すると良いでしょう。

必須かどうかを示す方法はアスタリスクを付与することです。
```html
<form>
  <p>アスタリスク（*）が付いているフィールドは必須です。</p>
  <label for="name">氏名*</label>
  <input type="text" name="name" id="name">
</form>
```
これは視覚的に意味を成しますが、スクリーンリーダのユーザにとって理解するのは簡単でありません。
`aria-required`属性でスクリーンリーダーユーザに必須項目であることを伝えることができます。
```html
<input type="text" name="name" id="name" aria-required="true">
```

年齢がどの範囲にあるべきかを示す方法としてツールチップやプレースホルダーを利用されることがあるでしょう。
最小値と最大値を指定するための`aria-valuemin`プロパティと`aria-valuemax`プロパティがありますが、きちんとサポートされていないようです。
よりサポートされている機能はHTMLの`placeholder`属性です。
```html
<label for="age">年齢*</label>
<input type="number" name="age" id="age" placeholder="1から150を入力してください" required aria-required="true">
```

全ての入力要素には必ず`<label>`を付与するようにしましょう。

スクリーンリーダーユーザにのみラベルを設ける場合は`aria-label`プロパティを使うと良いでしょう。`<label>`以外の要素をラベルづけしたい場合は`aria-labeledby`を使うか、別の情報をフォームに関連づけた上でその情報も同様に読み上げさせたい場合は`aria-describedby`を使うのも良いでしょう。

その他のフォーム要素の状態を示すのに便利なプロパティがあります。例えば、`aria-disabled="true"`はフォームフィールドが無効であることを示すことができます。

入力欄の無効化状態が変化する可能性が高い場合はその変化が起きた時点とその結果どうなったかを示すのも良いでしょう。

#### 意味論的でないボタンをボタンとして説明
`div`タグにボタンの役割を持たせるには`tabindex`とJavaScriptを利用することでキーボード操作を追加できます。

```html
<div tabindex="0">
  クリックしてください。
</div>
```

しかし、スクリーンリーダーユーザはボタンであることを理解できません。
そこで、WAI-ARIAロールを追加すると良いでしょう。

```html
<div tabindex="0" role="button">
  クリックしてください。
</div>
```

上記のようにすることで「クリックしてください。ボタン」のように読み上げられます。ただし、クリックイベントの処理など、ユーザが期待するネイティブなボタン機能を全て追加する必要があります。

#### 複雑なウィジェットを通じてユーザを案内する
一般的なUI機能として、意味的でない要素構造を特定できるロールが多数存在します。例えば、`combobox`、`slider`、`tabpanel`、`tree`などです。

タブにARIA機能を追加した例をみてみましょう。

<dl>
    <dt><code>tablist</code>、<code>tab</code>、<code>tabpanel</code></dt>
    <dd>タブのコンテナー、タブ自体、対応するタブパネルなど、タブ付きインタフェースの重要な領域を識別する</dd>
    <dt><code>aria-selected</code></dt>
    <dd>今どのタブが選別されているのか定める<br>別のタブがユーザから選択されるとJavaScriptを介して値が更新される</dd>
    <dt><code>aria-hidden</code></dt>
    <dd>スクリーンリーダーに読み上げられないように要素を隠す<br>別のタブがユーザから選択されるとJavaScriptを介して値が更新される</dd>
    <dt><code>tabindex="0"</code></dt>
    <dd>リスト項目にキーボードフォーカスを与えるために必要である</dd>
    <dt><code>aria-setsize</code></dt>
    <dd>要素が一連のものの一部であること、一連のものの中にいくつ項目があるのかをスクリーンリーダーに対して指定することができる</dd>
    <dt><code>aria-posinset</code></dt>
    <dd>要素が一連のものの中でどの位置にあるかを指定できる</dd>
</dl>

```html
<menu role="tablist">
    <li
        role="tab"
        aria-selected="true"
        aria-setsize="3"
        aria-posinset="1"
        tabindex="0">
        タブ1
    </li>
    <li
        role="tab"
        aria-selected="false"
        aria-setsize="3"
        aria-posinset="2"
        tabindex="0">
        タブ2
    </li>
    <li
        role="tab"
        aria-selected="false"
        aria-setsize="3"
        aria-posinset="3"
        tabindex="0">
        タブ3
    </li>
</menu>
<div>
    <article roll="tabpanel" aria-hidden="false">...</article>
    <article roll="tabpanel" aria-hidden="true">...</article>
    <article roll="tabpanel" aria-hidden="true">...</article>
</div>
```

上記のようにARIA属性を追加することでスクリーンリーダーユーザはタブをタブとして認識できるようになります。


## まとめ
この記事ではWAI-ARIAをまとめました。

フロントエンドエンジニアのWeb開発アクセシビリティ入門①｜HTMLは以下の記事にまとめています。

https://qiita.com/msuzuna/private/2b0114d4f76b62b95c7a

フロントエンドエンジニアのWeb開発アクセシビリティ入門②｜CSS/JavaScriptは以下の記事にまとめています。

https://qiita.com/msuzuna/items/5f5f33658d3a346e31fb

## 参考サイト
本記事を執筆するに当たって勉強したものは以下の通りです。
- MDN-WAI-ARIA の基本：https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics