# 読み上げを誤らせる、無効なARIA

Source: https://easeweb.dev/learn/valid-aria
Topics: ページの構造 > ARIAのロールと属性が正しい (WCAG 4.1.2)
The fix: ロール、属性、値のすべてをARIAが定義する言葉に直す（role="checkbox" と、"true" か "false" の aria-checked）か、独自のボックスをlabelの中のHTML標準のチェックボックスに置き換え、ARIAをなくします。テンプレートではなく、表示された後のマークアップを、すべての状態で確認してください。
Test with: screen reader, keyboard, mouse
References: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors https://www.w3.org/TR/wai-aria-1.2/#aria-checked https://www.w3.org/TR/using-aria/ https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/checkbox_role https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr https://dequeuniversity.com/rules/axe/4.10/aria-roles

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

ショップの購入手続きの画面で、「注文を確定する」ボタンの上に選択肢が一つあります。小さな四角いボックスと「ギフト包装にする」という言葉です。これは本物のチェックボックスではありません。ボックスとして描いた `div` に、クリックやSpaceキーでチェックマークを描くスクリプトと、支援技術に何であるかを伝えるARIAが付いています。そのARIAに、間違った言葉が一つ入っています。あるバージョンでは、スクリプトが状態を `aria-checked="yes"` と `aria-checked="no"` で書きます。スクリーンリーダー利用者がTabキーでボックスに移動すると、「ギフト包装にする、チェックボックス、チェックあり」と聞こえます。ギフト包装は不要なのでSpaceキーを押しますが、チェックありのまま読まれます。ボックスは空だったのに、今はチェックが入っています。別のバージョンでは、ロールが `aria-role="checkbox"` と書かれています。スクリーンリーダーは「ギフト包装にする」とだけ読み、ロールも状態も伝えず、Spaceキーを押しても何も読みません。

## 確認できる問題

ARIAは決まった語彙です。ロール、属性、属性が取れる値のそれぞれが仕様に定められていて、ブラウザーは知っている言葉だけでアクセシビリティツリーを作ります。それ以外は作成者の誤りで、仕様はその扱い方をブラウザーに定めています。作成者の意図を推し量るわけではありません。

- `aria-checked` が取る値は `"true"`、`"false"`、`"mixed"` です。オンかオフかとして公開する状態に未知の値があるとき、ARIAは空の値と `"undefined"` 以外をすべて true として扱うようブラウザーに定めています。Chromeはそのとおりに動き、`"no"` も `"yes"` と同じくチェックありと読まれます。ボックスの表示に関係なく、常にチェックありです。
- `aria-role` は属性ですらありません。ブラウザーはこれを無視するため、`div` は自身のロールである `generic` のままで、状態を持ちません。隣の正しい `aria-checked` も、generic の要素はチェックできないため無視されます。
- `role="check-box"` のように、ARIAが定義するロールを含まない `role` は、ロールが指定されていないものとして扱われ、結果は同じです。

どちらのバージョンも、4.1.2（名前・役割・値）を満たしません。ソースではどちらも設定しているように見えても、操作要素が支援技術に公開すべきロールか状態が、誤っているか欠けています。

## 利用者への影響

最初のバージョンは、手早い確認を通ってしまいます。Chromeでスクリーンリーダーを動かしてボックスにチェックを入れると、「チェックあり」と読まれます。問題が分かるのは空のボックスのときだけで、その状態はあまり確かめられません。同じ未知の値を別の扱い方をするブラウザーもあり、同じページで聞こえる内容が人によって違うこともあります。

## 自動チェックとAIの限界

axe はARIAの言葉を一つずつ仕様と照らし合わせるため、`aria-checked="no"`（`aria-valid-attr-value`）、`aria-role`（`aria-valid-attr`）、`role="check-box"`（`aria-roles`）を確実に報告します。ただし、確認するのは見つけたときの状態のページだけです。最初のクリックの後にだけ `"no"` を書くスクリプトは、読み込み時のページを調べても見つかりません。正しい言葉のロールが操作要素に合っているか、`aria-checked` がチェックマークに従っているかも、自動チェックには分かりません。手早い修正が状況を悪くすることもあります。axe が指摘した属性を削除すると、報告は消えますが、ロールのないボックスが残ります。`aria-role` を `role` に直しても、スクリプトが `"yes"` と `"no"` を書き続けるなら、一つの問題がもう一つに入れ替わるだけです。「念のため」HTML標準のチェックボックスに `aria-checked` を足すと、ブラウザーが無視する属性が増え、本来の状態と食い違った時点で、axe は `aria-conditional-attr` として報告します。

## 修正前：状態を "yes" と "no" で書く

```html
<div class="tick-box" role="checkbox" aria-checked="no" tabindex="0">
  ギフト包装にする
</div>
```

```js
const box = document.querySelector('.tick-box');
function toggle() {
  const ticked = box.getAttribute('aria-checked') !== 'yes';
  box.setAttribute('aria-checked', ticked ? 'yes' : 'no');
}
box.addEventListener('click', toggle);
box.addEventListener('keydown', (event) => {
  if (event.key === ' ') {
    event.preventDefault();
    toggle();
  }
});
```

```css
.tick-box[aria-checked="yes"]::before {
  content: "✓";
}
```

ロールは正しく、チェックマークも描かれます。しかし `"yes"` も `"no"` も `aria-checked` の値ではないため、Chromeはどちらもチェックありと読みます。空のボックスがチェックありと伝わります。

## 修正前：ロールを aria-role で書く

```html
<div class="tick-box" aria-role="checkbox" aria-checked="false" tabindex="0">
  ギフト包装にする
</div>
```

こちらのスクリプトは、正しく `"true"` と `"false"` を書きます。しかし `aria-role` はARIAの属性ではないため、要素にはロールがなく、ロールがないので `aria-checked` も無視されます。スクリーンリーダーは言葉を読むだけで、チェックできることも、チェックが入っているかどうかも伝えません。

## 修正後：ARIAの定義どおりに書く

```html
<div class="tick-box" role="checkbox" aria-checked="false" tabindex="0">
  ギフト包装にする
</div>
```

```js
const box = document.querySelector('.tick-box');
function toggle() {
  const ticked = box.getAttribute('aria-checked') !== 'true';
  box.setAttribute('aria-checked', String(ticked));
}
box.addEventListener('click', toggle);
box.addEventListener('keydown', (event) => {
  if (event.key === ' ') {
    event.preventDefault();
    toggle();
  }
});
```

```css
.tick-box[aria-checked="true"]::before {
  content: "✓";
}
```

`role="checkbox"` でボックスにロールが付き、`aria-checked` は `"true"` か `"false"` を持ちます。スクリーンリーダーは「ギフト包装にする、チェックボックス、チェックなし」と読み、Spaceキーかクリックの後は「チェックあり」と読みます。チェックマークは `aria-checked` そのものから描くため、見える状態と聞こえる状態がずれません。保存したカートのように、ギフト包装がすでに選ばれているときは、ページが `aria-checked="true"` を出力する必要があります。

## 修正後：HTML標準のチェックボックス

```html
<label class="gift-option">
  <input type="checkbox" name="gift-wrap">
  ギフト包装にする
</label>
```

HTML標準のチェックボックスは、ロール、状態、Spaceキーでの操作を最初から持ち、labelが名前を与えるため、ARIAもスクリプトも要りません。言葉をクリックしてもチェックが入ります。デザインに独自の見た目が必要なら、CSSで整えます。inputに `appearance: none` を指定すればボックスを描け、`:checked` でチェックマークを描けます。

## どの方法を選ぶか

どちらも基準を満たします。ARIAを直す方法は今のコンポーネントのまま直せますが、ロール、状態、Spaceキー、保存した状態のすべてを自分で正しく保つ必要があり、今後の編集で無効な言葉がまた入るおそれがあります。HTML標準のチェックボックスは、ARIAもスクリプトもなしに同じ役割を果たすため、書き間違える言葉が残らず、動作はブラウザーが保ちます。こちらを使ってください。ARIAを直す方法は、まだ構造を変えられないコンポーネントのために取っておきます。

## 実装の判断

**テンプレートではなく、表示されたものを確認する。** フレームワークやコンポーネントは、それぞれのやり方で値を属性に変えます。真偽値のpropsが `"true"` になることも、値のない属性になることも、出力されないこともあり、渡された文字列をそのまま渡すコンポーネントもあります。状態を変えるたびに、ブラウザーの開発者ツールで属性を確認してください。

**axe が報告する、ほかの無効なARIA。** 同じ問題は別の形でも現れ、それぞれに別のルールがあります。直し方は共通で、その場所にARIAが定義する言葉を使うか、ARIAの要らないHTML標準の要素を使います。

- ARIAが定義していないロールや、`role="input"` や `role="widget"` のような抽象ロール（`aria-roles`）。`checkbox` や `navigation` のような実在のロールか、`nav` のようなHTML要素を使う。
- ボタンの `aria-checked` のように、そのロールが対応していない属性（`aria-allowed-attr`）。押したままになるボタンには `aria-pressed` を使う。[押された状態が伝わらないトグルボタン](/ja/learn/toggle-buttons)を参照。`aria-role` のせいで `div` に残った `aria-checked` も、このルールで報告される。
- `<input type="checkbox">` の `aria-checked` のように、HTML標準の操作要素の状態を重ねて書いたARIA（`aria-conditional-attr`、本来の状態と食い違うと報告される）。削除する。ブラウザーはどちらにしても本来の `checked` を使う。
- `role="directory"` のような非推奨のロール（`aria-deprecated-role`）。`list` か `ul` を使う。
- ほかに名前のない要素の `aria-braillelabel` のように、対応する名前のない点字用のラベル（`aria-braille-equivalent`、確認対象として送られる）。まず通常の名前を付ける。

**一部のブラウザーでは動く書き間違い。** ChromeとSafariは `aria-labelledby` の代わりに `aria-labeledby` も受け付けますが、Firefoxは受け付けないため、Chromeではラベルが正しく見えても、Firefoxでは欠けることがあります。axe はこれを `aria-valid-attr` として報告します。Lを二つ書いてください。

**何も指さない参照。** ページにないidを指す `aria-labelledby` や `aria-describedby` は、ないものとして無視されます。axe はこれを問題として報告せず、`aria-valid-attr-value` の確認対象として送ります。属性があるときは常にそのidが存在するようにし、`#` を付けずに書いてください。

**言葉は正しくても使い方が誤っている場合は、別のガイドで扱います。** 名前を持てない `div` や `span` の名前は[読み上げられない、divやspanのaria-label](/ja/learn/aria-label-on-generic)、状態がまったくない場合は[オンかオフかが伝わらないトグルスイッチ](/ja/learn/toggle-switches)を参照してください。フォーカスを受けられる要素の `aria-hidden` は[Tabで届くのに読み上げられない、aria-hiddenの要素](/ja/learn/aria-hidden-focus)、項目のないリストのロールは[divやアイコン、手入力の番号で作った見た目だけのリスト](/ja/learn/list-markup)で扱っています。

## 修正の確認

1. ボックスが空の状態で axe を実行し、チェックを入れてもう一度実行する。どちらの状態でも `aria-valid-attr`、`aria-valid-attr-value`、`aria-roles`、`aria-allowed-attr` が何も報告しないことを確認する。
2. ブラウザーのアクセシビリティツリーで、両方の状態のボックスを確認する。ロールがチェックボックス、名前が「ギフト包装にする」で、チェック状態がチェックマークと一致する。
3. スクリーンリーダーでTabキーを押してボックスに移動し、Spaceキーとクリックでチェックを入れたり外したりする。変わるたびに新しい状態が読まれ、表示と一致する。
4. WindowsのNVDAとFirefox、MacのVoiceOverとSafariのように、別のブラウザーとスクリーンリーダーの組み合わせで、スクリーンリーダーでの確認を繰り返す。
5. ギフト包装を選んだ状態でページを再読み込みし、最初の読み上げがチェックありであることを確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。コンポーネントや、その状態を保存するコードを変更した際は、再度確認します。

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。クラス名は仮のもので、チェックボックスは、ARIAを手で書いた独自の操作要素の一例です。作成者の誤りの扱い方はブラウザーごとに違い、バージョンによっても変わるため、一つのブラウザーで正しく聞こえても、確かめられたことはわずかです。言葉が正しいARIAが、使い方も正しいとは限りません。自動チェックで何も報告されなくても、ロールが操作要素に合っているか、状態が画面に従っているかは分かりません。対象とする支援技術で、操作全体を確認してください。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
