aria-labelでなんでも解決しようとしない

「スクリーンリーダーではこう読んでほしい」という意図で、`aria-label`属性が使われている場面をたまに見ます。

```html
<!-- 注意: これは適切でない例です。理由は後述します -->
<span aria-label="10月13日">10/13</span>
```

`aria-label` 属性は、たまに「スクリーンリーダーによる読まれ方を指定する」属性のように思われてしまっているようです。この理解のされ方は、実はあまり正確なものではありません。

そもそも`aria-label`という名前が `aria-` で始まているということは、WAI-ARIAで定義されている属性です。こういった属性は使う必要がなければ使わないに越したことはないという話を以前に書きました。

https://qiita.com/ymrl/items/6c9c059208ea11e6d7bc

`aria-label` 属性も例外でなく、使い方を間違えると、意図した効果を得られないどころか、思わぬ形で作用してアクセシビリティを下げてしまうこともあります。

なお、 `aria-label` 属性と良く似た性質を持つ属性として `aria-labelledby` 属性があります。こちらは `aria-label` 属性とは違い、他の要素の `id` 属性の値を指定して、要素を参照して使用する属性です。ほかの要素を参照して使うため、使われる場所は限定的で、`aria-label` 属性ほど多用されていない印象をもっています。この記事では主に `aria-label` 属性の説明をしますが、性質は非常に似ているため、興味のある人はぜひ　`aria-labelledby` 属性についても調べてみてください。

## `aria-label` 属性とは何なのか

[WAI-ARIAの仕様書](https://www.w3.org/TR/wai-aria-1.2/)（[日本語訳](https://momdo.github.io/wai-aria-1.2/)）を読んでも、`aria-label` 属性の説明はすこし難解な印象で、これだけでは理解が難しいかもしれません。

> 現在の要素にラベルを付ける文字列の値を定義する。関連する[aria-labelledby](https://momdo.github.io/wai-aria-1.2/#aria-labelledby)を参照のこと。
>
> aria-labelの目的は[aria-labelledby](https://momdo.github.io/wai-aria-1.2/#aria-labelledby)と同じである。これは、ユーザーにオブジェクトの認識可能な名前を提供する。ラベルをマッピングする最も一般的な[アクセシビリティAPI](https://momdo.github.io/wai-aria-1.2/#dfn-accessibility-api)は、[アクセシブルな名前](https://momdo.github.io/wai-aria-1.2/#dfn-accessible-name)プロパティである。
>
> [Accessible Rich Internet Applications (WAI-ARIA) 1.2 日本語訳](https://momdo.github.io/wai-aria-1.2/#aria-label)

これを理解するためには、WAI-ARIAが提供する、スクリーンリーダーのような支援技術に情報を提示する仕組みについて、ある程度の理解が必要でしょう。

スクリーンリーダーのような支援技術は、引用部にも登場する「**アクセシビリティAPI**」を通して、Webコンテンツにアクセスします。そしてWebコンテンツをアクセシビリティAPIにどのように連携するのかを定義しているのが、「WAI-ARIAスイート」です。

WebコンテンツをアクセシビリティAPIから利用できるように、WebブラウザはWAI-ARIAスイートに基いて、内部的に **アクセシビリティツリー（Accessibility Tree）** または **アクセシビリティオブジェクトモデル (AOM: Accessibility Object Model)** と呼ばれるものを生成します。これはWebコンテンツに含まれる様々な要素の情報を、アクセシビリティAPIのために整形したものです。この内容がアクセシビリティAPIに渡され、支援技術で利用できるようになっているというわけです。

このアクセシビリティツリー上で非常に重要なプロパティが **Nameプロパティ** 、すなわち「**アクセシブルな名前（Accessible name）**」です。

アクセシブルな名前は、ユーザーが要素を識別するのに使う情報でもあり、そしてコンテンツそのものでもあります。たとえば、以下のものはすべてアクセシブルな名前として使われます。

- `<a href="...">` 要素内のテキスト
- `<button>` 要素内のテキスト
- `<input>` 要素に紐付けられた、`<label>` 要素の内容
- `<img>` 要素の `alt` 属性の内容

```html
<a href="https://example.com/">この部分が a 要素の名前になります</a>
<button>この部分が button 要素の名前になります</button>
<label for="input">この部分が input 要素の名前になります</label>
<input id="input" type="text" name="input">
<img src="./example.png" alt="この部分が img 要素の名前になります">
```

アクセシブルな名前がどうやって決まるのかは、[Accessible Name and Description Computation](https://www.w3.org/TR/accname-1.2/)（[日本語訳](https://momdo.github.io/accname-1.2/)）に定義されています。といっても、普通にWebコンテンツを作る上では、HTMLの仕様に沿って良しとされるHTMLの書き方をしていれば、適切な名前となるようにできています。

ここで重要なのは、**`aria-label`属性（や、`aria-labelledby` 属性）は、アクセシブルな名前を決めるプロセスで強く優先される** ということです。つまり、`aria-label`属性を使うことで、「普通のHTMLの書き方」とは違う名前の付け方をすることができ、スクリーンリーダーのような支援技術のユーザーに伝えることができます。

## `aria-label` 属性が使用できないロール

`aria-label` 属性を使う上での大きな落とし穴として、`aria-label` 属性を使用できないWAI-ARIAロール（要素の種類）が数多く存在するという点があります。

:::note warn
「そもそもWAI-ARIAロールとは？」と思った方は、[先日の記事](https://qiita.com/ymrl/items/6c9c059208ea11e6d7bc)もお読みください
:::

> ロールで使用される：
> 次のロールを除く基礎マークアップのすべての要素： [caption](https://momdo.github.io/wai-aria-1.2/#caption), [code](https://momdo.github.io/wai-aria-1.2/#code), [deletion](https://momdo.github.io/wai-aria-1.2/#deletion), [emphasis](https://momdo.github.io/wai-aria-1.2/#emphasis), [generic](https://momdo.github.io/wai-aria-1.2/#generic), [insertion](https://momdo.github.io/wai-aria-1.2/#insertion), [paragraph](https://momdo.github.io/wai-aria-1.2/#paragraph), [presentation](https://momdo.github.io/wai-aria-1.2/#presentation), [strong](https://momdo.github.io/wai-aria-1.2/#strong), [subscript](https://momdo.github.io/wai-aria-1.2/#subscript), [superscript](https://momdo.github.io/wai-aria-1.2/#superscript)
> 
> [Accessible Rich Internet Applications (WAI-ARIA) 1.2 日本語訳](https://momdo.github.io/wai-aria-1.2/#aria-label)

これらのロールは、WAI-ARIA仕様書内の「[5.2.8.6 名前を付けることができないロール（名前は禁止されている）](https://momdo.github.io/wai-aria-1.2/#namefromprohibited)」に挙げられているものと同じです。`aria-label` 属性は名前を付けるための属性なので、それが禁止されているロールでは使用できないということでしょう。

WAI-ARIAのロールは `role` 属性によって指定されるだけでなく、 `role` 属性が無くてもHTML要素に割り当てられている「暗黙のロール」が存在します。そのため、 **上記のロールを暗黙的に持つHTML要素では、`role` 属性の指定なしに `aria-label` 属性を使用できない** ということになります。

|ロール|暗黙的にロールを持つHTML要素[^implicitrole]|
|-----:|:----------------------|
|caption|`<caption>` `<figcaption>`|
|code| `<code>`|
|deletion|`<del>` `
`|
|emphasis|`<em>`|
|generic |`<b>` `<bdi>` `<bdo>` `<body>` `<data>` `<div>` `<i>` `<pre>` `<q>` `<samp>` `<small>` `<span>` `<u>`|
|insertion|`<ins>`|
|paragraph|`<p>`|
|presentation|（なし）|
|strong|`<strong>`|
|subscript|`<sub>`|
|superscript|`<sup>`|

[^implicitrole]: 特定の条件下でのみ、ここに挙げたロールとなる要素については記載を省略しています

つまり、これらの要素では、 `role` 属性によって名前を付けられるロールにしないかぎり、 `aria-label` 属性を使用できません。もちろん、`aria-label` 属性を使いたいからといって、適切でない値の `role` 属性をつけていいというわけでもありません。

### 使用できないロールで `aria-label` 属性を使用した場合、スクリーンリーダーによって挙動が異なる

記事の冒頭で挙げた例は、日付を表す「10/12」が「じゅう スラッシュ じゅうに」と読まれることを防ぐために `aria-label="10月12日"` を付けたものでした。

```html
<!-- 注意: span要素は暗黙のgenericロールを持つため、aria-label属性の使用は適切ではない -->
<span aria-label="10月13日">10/13</span>
```

これをスクリーンリーダーで読ませても、スクリーンリーダーによって読まれ方は違うものになってしまいます。

|スクリーンリーダー|読まれ方|
|-|-|
|PC-Talker クリエイター版| 「10/13」[^pctalker] |
|NVDA|「10スラッシュ13」|
|macOS VoiceOver|「10月13日、グループ」→「10スラッシュ13」→「終わり、10月13日、グループ」|
|iOS VoiceOver|「10スラッシュ13」|
|Android TalkBack|「10月13日」|

[^pctalker]: 音声出力機能のないPC-Talkerクリエイター版を使用したため、「10/13」がどう発音されるかまでは検証できていません。

「10月13日」と素直に読めたのはAndroidのTalkBackだけでした。macOSのVoiceOverでは、「10月13日」という読み上げ方が入っているものの、余計な「グループ」の読み上げが加わってしまいました。そして、[日本の視覚障害者にユーザーが多い](https://jbict.net/survey/at-survey-03) PC-Talker、NVDA、iOSのVoiceOverでは、すべて `aria-label`属性の内容は読み上げられ方に反映されませんでした。

つまり、 **`aria-label` 属性をつけてアクセシビリティを高めたつもりになっていても、実際にはその内容はスクリーンリーダーを使うユーザーには届いていなかった** ということが起きてしまうのです。

## `aria-label` 属性の使用をなるべく避ける

ロールの制約のほかにも、`aria-label`属性を使うことで問題が起きるケースはいろいろあります。

- 親要素に付けられた `aria-label` 属性によって子要素の内容が読めなくなってしまった
- `aria-label`属性によって読み上げられた内容と表示されている内容が異なり、視覚とスクリーンリーダーによる読み上げを併用している弱視者が混乱してしまった
- ページの改修時に`aria-label`属性の内容を変更しそびれて、内容に食い違いが発生してしまった

やはり、WAI-ARIAの属性は強力なものであり、迂闊に使うべきではありません。どうしてもそうせざるを得ない場合にのみ使うべきです。WAI-ARIAの仕様を調べるべきというのは当然ですが、それ以外にもアクセシビリティに取り組むうえで認識しておくべきことがありそうです。

### スクリーンリーダーの読み方は制御できない

記事の冒頭に挙げた例は、スクリーンリーダーの読み上げ方に違和感があったので、 `<span>` 要素に `aria-label` 属性をつけたというものでした。記号や漢字の読み方が変になってしまうのは、スクリーンリーダーでよく発生する問題です。SNS上でスクリーンリーダーのユーザーの声を見ていると、やはり漢字や記号の読み方がおかしいというのは気になる事象のようです。

しかし、これに対してWebコンテンツを作る側ができる有効な対策は、いまのところありません。スクリーンリーダーはOSや開発元によっていろいろな種類があり、さらにいろいろな設定により多種多様な読まれ方をします。スクリーンリーダーの読み方を書き手の意図通りにする方法は、ありません。

多くのスクリーンリーダーには、文字を1文字ずつ説明してくれる「詳細読み」機能があります。これを使うと変な読み方をした場合でも、どんな文字が書かれているかを調べられます。たとえば漢字の読み方がおかしいからといって、ひらがなにしてしまうと、「詳細読み」によって得られる「文字の意味」という情報が欠落してしまいます。ほかにも、アクセントがおかしくなったり点字の表示が適切でなくなったりすることがあるそうです。

https://a11y-guidelines.freee.co.jp/faq/articles/p0001.html

### 視覚的な情報とスクリーンリーダー向けの情報はなるべく同じにする

`aria-label` を使って、視覚的に表示されていることよりも丁寧な内容をスクリーンリーダーに伝えようとする例もたまに見かけます。

```html
<h2>「食欲の秋」キャンペーン</h2>
<p>抽選で10名様に、お食事券1万円ぶんをプレゼント！</p>
<!-- 視覚的な表示とスクリーンリーダーの読み上げが食い違ってしまう -->
<a href="/campaign" aria-label="「食欲の秋」キャンペーンページ">お申し込みはこちら</a>
```

たとえば上の例では、視覚的には「お申し込みはこちら」と書かれているところが、スクリーンリーダーでは「『食欲の秋』キャンペーンページ」と読み上げられるようになっているはずです。

これはスクリーンリーダーの情報だけでWebを閲覧している人にとっては、違和感を感じるものではないはずです。

ところが、スクリーンリーダーを使う人すべてが全盲 = 完全に目が見えていないわけではありません。弱視やロービジョンと呼ばれる、視力が著しく低かったり視野の一部が欠けていたりなど様々な見づらさを抱えた人もスクリーンリーダーを使用しています。また、ディスレクシアと呼ばれる、文字を読むことが難しい障害をもつ人もスクリーンリーダーを利用することがあります。読み上げる内容と視覚的に提示される内容が違ってしまうことで、こういった人たちは混乱してしまうかもしれません。

またこういった、`aria-label`属性の内容と視覚的表示が一致しない状況は、画面の改修時に `aria-label`属性の内容の変更が漏れてしまうことによっても発生します。そういったトラブルをなるべく避ける意味でも、本当に必要な場所以外では `aria-label` の使用を避けておくのが望ましいです。

```html
<!-- button 要素内に「保存」というテキストが入っているので、 aria-label="保存" は必要ない -->
<button>保存</button>

<!-- label要素を使えば、 input 要素や select 要素や textarea 要素にアクセシブルな名前をつけられる -->
<label for="username">お名前</label>
<input type="text" id="username" name="username">

<!-- 画像のアクセシブルな名前は、 alt 属性でつけられる -->
<img src="neko.jpg" alt="ネコの写真">

<!-- 顔文字やアスキーアートにはアクセシブルな名前をつける方法がないので、 role="img" と一緒に aria-label 属性をつける -->
<span role="img" aria-label="ちゃぶ台をひっくり返す顔文字">
(╯°□°)╯︵ ┻━┻
</span>
```