# 画面に見えるのにスクリーンリーダーが読まないテキスト

Source: https://easeweb.dev/learn/hidden-content
Topics: ページの構造 > 画面に見えるものと、スクリーンリーダーが読むものが一致する (WCAG 1.3.1)
The fix: バッジは隠した写真のリンクの外に、売り切れのカードにだけ書き、画面とスクリーンリーダーが同じ言葉を受け取るようにします。または、バッジは写真の一部として残し、同じ言葉を視覚的に隠したテキストとして商品のリンクに入れます。どちらの場合も、すべてのカードにバッジを書いて透明にする方法はとりません。opacityは見た目を消すだけで、読み上げには残るためです。
Test with: screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F92 https://www.w3.org/TR/wai-aria-1.2/#aria-hidden https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden https://www.w3.org/WAI/WCAG22/Techniques/css/C7 https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body

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

## 場面

マグの店が、商品をカードの一覧で並べています。カードには写真、リンクになったマグの名前、価格があります。売り切れたマグには、買い物客が最初に見る写真の上に「売り切れ」のバッジを描きます。写真は名前と同じページへのリンクなので、よく勧められるとおり、スクリーンリーダーから隠してTabの移動先からも外し、カードごとのリンクを一つにしています。バッジは、その写真のリンクの中にあります。

画面では、「釉薬のマグ」が売り切れなのは一目で分かります。ところが、スクリーンリーダーで一覧を読むと「斑点模様のマグ、リンク、£18。釉薬のマグ、リンク、£22。背の高いマグ、リンク、£20」となり、釉薬のマグをほかと区別するものがありません。買い物客は商品を開き、商品ページで初めて買えないことを知ります。

## 確認できる問題

達成基準1.3.1「情報及び関係性」は、見た目で示している情報を、コードから判断できるか、テキストで得られるようにすることを求めています。「このマグは売り切れ」というのも情報の一つで、ここではバッジにしか書かれていません。

`aria-hidden="true"` は、要素とその中身すべてを支援技術に渡す内容から外し、それ以外は何もしません。名前のリンクと重複する写真のリンクには適切です。しかし、中のバッジも同じ扱いになるため、バッジが示す情報は、スクリーンリーダーが受け取るテキストにありません。

## 利用者への影響

この問題に出会うのは、ページを音声や点字で受け取る人すべてです。目で見て確かめるだけでは気づけません。画面を見ながらマウスやキーボードで操作する人は、この問題に出会いません。

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

axeには、`aria-hidden` の中の見えるテキストを調べるルールがありません。`aria-hidden-focus` が調べるのはフォーカスを受けるものだけで、バッジは受けません。隠した内容が必要かどうかは人にしか判断できないため、この診断では、`aria-hidden` の中の見える言葉を確認に回します（`ew:aria-hidden-text-manual-check`）。ただし、記号、アイコンの字形、同じカードでもう一度読まれる言葉は除きます。透明にしたり画面の外に移したりしたのに読まれるテキストは、まったく報告しません。視覚的に隠したテキストは、画面にない言葉をスクリーンリーダーに渡す正当な方法でもあり、意図して隠した言葉と紛れ込んだ言葉をコードでは区別できないからです。body要素の `aria-hidden` はaxeの `aria-hidden-body` として報告し、`role="presentation"` を付けたデータテーブルは確認に回します（ANDI）。AIアシスタントに「重複している画像のリンクをスクリーンリーダーから隠して」と頼むと、中にほかに何があるかを見ずに、リンクに `aria-hidden` を加えることがあります。

## 修正前：隠した写真のリンクの中のバッジ

```html
<ul class="products">
  …
  <li class="card">
    <a class="photo" href="/mugs/glazed" aria-hidden="true" tabindex="-1">
      <img src="/img/glazed-mug.jpg" alt="">
      <span class="badge">Sold out</span>
    </a>
    <a href="/mugs/glazed">
      Glazed mug
    </a>
    <p class="price">£22</p>
  </li>
  <li class="card">
    <a class="photo" href="/mugs/tall" aria-hidden="true" tabindex="-1">
      <img src="/img/tall-mug.jpg" alt="">
    </a>
    <a href="/mugs/tall">Tall mug</a>
    <p class="price">£20</p>
  </li>
</ul>
```

```css
.card {
  position: relative;
}
.badge {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
}
```

重複する写真のリンクを隠すのは正しいことですが、バッジも一緒に隠れます。釉薬のマグが売り切れだと伝える言葉は `aria-hidden` の中にしかないため、スクリーンリーダーは、そのカードを背の高いマグのカードとまったく同じように読みます。

## 修正後：写真の外のバッジを、売り切れのカードにだけ書く

```html
<ul class="products">
  …
  <li class="card">
    <a class="photo" href="/mugs/glazed" aria-hidden="true" tabindex="-1">
      <img src="/img/glazed-mug.jpg" alt="">
    </a>
    <span class="badge">Sold out</span>
    <a href="/mugs/glazed">
      Glazed mug
    </a>
    <p class="price">£22</p>
  </li>
  <li class="card">
    <a class="photo" href="/mugs/tall" aria-hidden="true" tabindex="-1">
      <img src="/img/tall-mug.jpg" alt="">
    </a>
    <a href="/mugs/tall">Tall mug</a>
    <p class="price">£20</p>
  </li>
</ul>
```

```css
.card {
  position: relative;
}
.badge {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
}
```

テンプレートは、マグが売り切れのときだけ、写真のリンクの中ではなくその隣にバッジを書き、CSSは引き続き写真の上に描きます。画面に表示されるものとスクリーンリーダーが読むものが同じ要素から生まれ、「売り切れ、釉薬のマグ、リンク、£22」と読まれます。ページを開いている間に在庫が変わる場合は、カードごとにバッジを一つ置き、全員から一度に隠す `hidden` 属性で切り替えます。opacityは使いません。

## 修正後：商品のリンクの言葉に状態を入れる

```html
<ul class="products">
  …
  <li class="card">
    <a class="photo" href="/mugs/glazed" aria-hidden="true" tabindex="-1">
      <img src="/img/glazed-mug.jpg" alt="">
      <span class="badge">Sold out</span>
    </a>
    <a href="/mugs/glazed">
      Glazed mug
      <span class="visually-hidden">, sold out</span>
    </a>
    <p class="price">£22</p>
  </li>
  <li class="card">
    <a class="photo" href="/mugs/tall" aria-hidden="true" tabindex="-1">
      <img src="/img/tall-mug.jpg" alt="">
    </a>
    <a href="/mugs/tall">Tall mug</a>
    <p class="price">£20</p>
  </li>
</ul>
```

```css
.card {
  position: relative;
}
.badge {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

バッジは隠した写真の一部として元の場所に残し、売り切れのカードにだけ置きます。同じ言葉を視覚的に隠したテキストとして商品のリンクに入れるため、リンク自体が「釉薬のマグ、売り切れ」と読まれ、リンクからリンクへ移動するスクリーンリーダーの利用者にもそう聞こえます。リンクの名前は見えている名前で始まるため、音声操作の利用者は「釉薬のマグをクリック」と言えます。テンプレートは両方を同じ在庫の値から書く必要があり、そうしないと二つがずれていきます。

## どの方法を選ぶか

どちらも1.3.1を満たし、どちらもバッジを当てはまる場所にだけ書きます。写真の外のバッジは、見えるものと読まれるものが一つの要素なので、二つを揃え続ける手間がありません。マークアップでバッジを動かせるなら、こちらを選びます。リンクに言葉を入れる方法は、マークアップを元のまま保ち、リンクで移動する人に聞こえる場所に状態を置きます。その代わり、同じ言葉を二か所に書くことになり、テンプレートでバッジと揃え続ける必要があります。どちらを選んでも、当てはまらないものは `hidden` か `display: none` で隠し、opacityは使いません。

## 実装の判断

**すべてのカードに書いて透明にしたバッジ。** バッジを写真のリンクの外に移すだけでは、修正は半分です。テーマがすべてのカードにバッジを書き、在庫のあるマグでは `opacity: 0` で透明にすると、逆の失敗になります。opacityは、透明な文字色や画面の外への配置と同じく、バッジを目から隠すだけでテキストには残すため、画面に表示されるのは一つでも、スクリーンリーダーはすべてのマグの前で「売り切れ」と読みます。バッジは当てはまる場所にだけ書くか、全員から隠す `hidden` 属性か `display: none` で隠します。

**視覚的に隠したテキストが問題なのではありません。** スクリーンリーダーだけが受け取るテキストは、リンクの「売り切れ」や、ページ番号の前の「ページ」のように、画面の内容を繰り返したり補ったりするなら適切です。問題になるのは、透明にしたバッジのように、画面にないことを伝えるときです。上のような共通のクラスを一つ使い、テキストを画面の遠く外へ移す方法は避けます。レイアウトによっては、ページが横にスクロールしてしまうためです。

**aria-hiddenの中にほかに何があるか。** 重複する写真のリンクは `aria-hidden` に向いた場所ですが、写真だけを含む場合に限ります。写真の上に描いた価格、評価、「残り2点」も、バッジと同じ問題を抱えます。`aria-hidden` の中にフォーカスを受けるものがある場合は別の問題です。[Tabで届くのに読み上げられない、aria-hiddenの要素](/ja/learn/aria-hidden-focus)を参照してください。

**body要素のaria-hidden。** モーダルやメニューのスクリプトが、`body` に `aria-hidden="true"` を付けたまま外し忘れることがあります。現在のChromiumとWebKit（それぞれChromeとSafariのエンジン）、バージョン139以降のFirefoxは、ARIA 1.3の草案が求めるとおり、body要素に付いたaria-hiddenを無視します。ただし、古いブラウザーでは、支援技術からページ全体が隠れることがあります。属性を外し、モーダルの背後のページは `inert` かHTML標準のダイアログで隠します。[背後にフォーカスが残るモーダル](/ja/learn/modal-focus-management)を参照してください。

**presentationにしたテーブル。** サイズ表のようなデータテーブルに `role="presentation"` や `role="none"` を付けると、行、列、見出しが失われ、スクリーンリーダーはセルをただの言葉の並びとして読みます。W3Cが1.3.1の失敗例として挙げるF92にあたります。セルに見出しが必要なテーブルからはロールを外し、レイアウトに使うテーブルにだけ残します。ロールを外したリストには別のガイドがあります。[divやアイコン、手入力の番号で作った見た目だけのリスト](/ja/learn/list-markup)を参照してください。

**画像。** 飾りだけの画像には、ここの写真のように、`aria-hidden` ではなく `alt=""` を付けます。[ファイル名や同じ言葉が読まれる装飾画像](/ja/learn/decorative-images)を参照してください。

## 修正の確認

1. ブラウザーのアクセシビリティツリーで、`aria-hidden="true"` の要素を一つずつ確認し、必要な言葉を含むものがあれば、同じカードのほかの場所でもその言葉が読まれることを確かめる。
2. スクリーンリーダーで商品一覧を先頭から読み、売り切れのマグが売り切れと読まれ、ほかのマグはそう読まれないことを確認する。
3. スクリーンリーダーを使い、Tabで商品のリンクを移動し、売り切れの商品がリンクの中か、その直前でそう伝えられることを確認する。
4. パソコンとスマートフォンの両方で、画面と読み上げた内容をカードごとに比べ、表示される状態がすべて読まれ、読まれた内容がすべて画面にあることを確認する。
5. カードのテンプレートを読み、バッジが当てはまる場所にだけ書かれるか、`hidden` で切り替えられ、opacityで透明にしていないことを確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。カードのコンポーネント、テーマ、在庫の表示を変更した際は、再度確認します。

## 制限

このガイドの対象は、表示されているのに読まれない言葉と、読まれるのに表示されていない言葉です。`aria-hidden` の中のフォーカスを受ける要素は[Tabで届くのに読み上げられない、aria-hiddenの要素](/ja/learn/aria-hidden-focus)で、装飾画像は[ファイル名や同じ言葉が読まれる装飾画像](/ja/learn/decorative-images)で扱います。隠した言葉が必要かどうか、スクリーンリーダー向けに意図して隠したものかどうかは、ページごとに人が判断する必要があります。カードやリンクの読み方はスクリーンリーダーや点字ディスプレイによって違うため、利用者が使う組み合わせで確認してください。

---

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