# CSSだけで描かれ、テキストのないアイコンと画像

Source: https://easeweb.dev/learn/css-images
Topics: 画像とメディア > CSSで描いたアイコンや画像も、意味を伝える (WCAG 1.1.1, 1.3.1)
The fix: 情報をCSSだけでなくHTMLに置きます。CSSの絵はそのままにして各セルに見えない言葉を加えるか、絵を alt のある img としてHTMLに移すか、セルに言葉を書いて絵はその横の飾りにします。
Test with: screen reader, styles off
References: https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F3 https://www.w3.org/WAI/WCAG22/Techniques/css/C9 https://developer.mozilla.org/en-US/docs/Web/CSS/background-image#accessibility

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

## 場面

あるネットショップ作成サービスは、スターター、ショップ、プロの3つのプランを表で比べています。行は機能で、カード決済、独自ドメイン、ギフトカードと並びます。各セルには、プランにその機能が含まれていれば緑のチェック、含まれていなければ灰色のダッシュが表示されます。見える人は、ショップで独自ドメインが加わり、ギフトカードはプロだけだと一目で分かります。スクリーンリーダーの利用者がショップの列を下へ移動すると、「カード決済、空白」「独自ドメイン、空白」「ギフトカード、空白」と聞こえます。表のどのセルも同じように読まれます。見出しはすべてそろっているのに、セルが本来伝えるはずの、プランに機能が含まれるかどうかという情報だけが抜け落ちています。

## 確認できる問題

チェックとダッシュは、どれもCSSで描かれています。セルには `yes` か `no` のクラスがあり、スタイルシートがそれぞれのクラスに背景画像を指定しています。CSSの背景画像には代替テキストを持たせる方法がなく、支援技術にもまったく伝わらないため、スクリーンリーダーにとってセルは空です。これは1.1.1 非テキストコンテンツを満たしません。チェックは情報を伝える非テキストコンテンツなのに、代替テキストがないからです。W3Cはこれを失敗例F3として挙げています。1.3.1 情報及び関係性は、見た目で示す情報をコードから判別できるか、テキストで得られることを求めています。ユーザースタイルシートに置き換えたときなど、見た目が変わっても情報が残るようにするためです。表の構造は正しく、すべての行と列に見出しがありますが、各セルが伝える内容はクラス名とスタイルシートにしかありません。HTMLのどこにも「含まれる」「含まれない」とは書かれておらず、スタイルを無効にしたり置き換えたりすると、答えは消えます。

## 利用者への影響

ブラウザーは、印刷時に「背景のグラフィック」（Chrome）や「背景画像を印刷する」（Firefox）をオンにしない限り背景画像を印刷しないため、印刷した比較表からもチェックが消えます。リーダー表示、テキストだけのブラウザー、スタイルを無効にしたページでも、同じように空のセルになります。

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

axe などのツールは表の見出しを確認し、合格にします。背景画像に意味があるかどうかをスタイルシートまで確かめることはないため、空のセルが並ぶ表も合格になります。ANDIは、背景画像のあるページで、それが飾りであることを確認するよう注意を出しますが、どの画像が情報を伝えているかは判断できません。この診断では、リンクやボタンの外にある空の要素がCSSの背景画像やマスク画像、アイコンフォントの文字を表示していると、人が確認する候補として報告します。対象はアイコンほどの大きさの要素と、この表のような表のセルやリストの項目で、表やリストごとに1件にまとめます。AIによる手早い修正では、各 `td` に `aria-label` を付けることがありますが、表のセルの `aria-label` を読まないスクリーンリーダーもあります。`title` も確実には読まれません。各絵が何を意味するかは、プランを知る人にしか判断できません。

## 修正前：空のセルの背景画像で描いたチェックとダッシュ

```html
<table class="plans">
  <thead>
    <tr>
      <td></td>
      <th scope="col">スターター</th>
      <th scope="col">ショップ</th>
      <th scope="col">プロ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">カード決済</th>
      <td class="yes"></td>
      <td class="yes"></td>
      <td class="yes"></td>
    </tr>
    <tr>
      <th scope="row">独自ドメイン</th>
      <td class="no"></td>
      <td class="yes"></td>
      <td class="yes"></td>
    </tr>
    <tr>
      <th scope="row">ギフトカード</th>
      <td class="no"></td>
      <td class="no"></td>
      <td class="yes"></td>
    </tr>
  </tbody>
</table>
```

```css
.plans td { text-align: center; }
.plans .yes { background: url(/icons/tick.svg) center / 20px no-repeat; }
.plans .no { background: url(/icons/dash.svg) center / 20px no-repeat; }
```

HTMLでは、データセルはどれも空です。チェックとダッシュはスタイルシートの中にしかなく、そこには言葉を持たせられません。

## 修正後：各セルに見えない言葉を入れる

```html
<table class="plans">
  <thead>
    <tr>
      <td></td>
      <th scope="col">スターター</th>
      <th scope="col">ショップ</th>
      <th scope="col">プロ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">カード決済</th>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
    </tr>
    <tr>
      <th scope="row">独自ドメイン</th>
      <td class="no"><span class="visually-hidden">含まれない</span></td>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
    </tr>
    <tr>
      <th scope="row">ギフトカード</th>
      <td class="no"><span class="visually-hidden">含まれない</span></td>
      <td class="no"><span class="visually-hidden">含まれない</span></td>
      <td class="yes"><span class="visually-hidden">含まれる</span></td>
    </tr>
  </tbody>
</table>
```

```css
.plans td { text-align: center; }
.plans .yes { background: url(/icons/tick.svg) center / 20px no-repeat; }
.plans .no { background: url(/icons/dash.svg) center / 20px no-repeat; }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

絵はそのままで、各セルに同じことを表す言葉が加わります。言葉は切り抜いて見えなくしていますが、アクセシビリティツリーには残っています。スクリーンリーダーは「ギフトカード、含まれない」と読みます。表の見た目はまったく変わりません。言葉はHTMLにあるため、スタイルを無効にしても残り、ページと一緒に翻訳されます。ただし、チェックと同じく印刷はされません。

## 修正後：チェックを alt のある画像にする

```html
<table class="plans">
  <thead>
    <tr>
      <td></td>
      <th scope="col">スターター</th>
      <th scope="col">ショップ</th>
      <th scope="col">プロ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">カード決済</th>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
    </tr>
    <tr>
      <th scope="row">独自ドメイン</th>
      <td><img src="/icons/dash.svg" alt="含まれない" width="20" height="20"></td>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
    </tr>
    <tr>
      <th scope="row">ギフトカード</th>
      <td><img src="/icons/dash.svg" alt="含まれない" width="20" height="20"></td>
      <td><img src="/icons/dash.svg" alt="含まれない" width="20" height="20"></td>
      <td><img src="/icons/tick.svg" alt="含まれる" width="20" height="20"></td>
    </tr>
  </tbody>
</table>
```

```css
.plans td { text-align: center; }
.plans td img { vertical-align: middle; }
```

チェックとダッシュをスタイルシートからHTMLに移します。HTMLの画像なら代替テキストを持たせられます。スクリーンリーダーは「ギフトカード、含まれない」と読み、スクリーンリーダーによってはその後に「画像」や「グラフィック」と加えます。表の見た目は同じで、絵が背景ではなく内容になったため、印刷もされます。

## 修正後：セルに言葉を書き、絵はその横に置く

```html
<table class="plans">
  <thead>
    <tr>
      <td></td>
      <th scope="col">スターター</th>
      <th scope="col">ショップ</th>
      <th scope="col">プロ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">カード決済</th>
      <td class="yes">含まれる</td>
      <td class="yes">含まれる</td>
      <td class="yes">含まれる</td>
    </tr>
    <tr>
      <th scope="row">独自ドメイン</th>
      <td class="no">含まれない</td>
      <td class="yes">含まれる</td>
      <td class="yes">含まれる</td>
    </tr>
    <tr>
      <th scope="row">ギフトカード</th>
      <td class="no">含まれない</td>
      <td class="no">含まれない</td>
      <td class="yes">含まれる</td>
    </tr>
  </tbody>
</table>
```

```css
.plans td { text-align: center; }
.plans .yes { padding-left: 28px; background: url(/icons/tick.svg) 4px center / 20px no-repeat; }
.plans .no { padding-left: 28px; background: url(/icons/dash.svg) 4px center / 20px no-repeat; color: #5f6368; }
```

そのプランに機能が含まれるかどうかが各セルの言葉で分かり、チェックやダッシュは言葉の左に移ります。背景画像は飾りになるため、読み込みに失敗しても、印刷で省かれても、スタイルと一緒に消えても、何も失われません。スクリーンリーダーは「ギフトカード、含まれない」と読みます。この方法では見た目が変わります。表は広くなり、言葉が並びますが、比較表のダッシュの意味に迷う人にも分かりやすくなります。

## どの方法を選ぶか

3つの方法はどれも情報をHTMLに置くため、1.1.1と1.3.1を満たします。見えない言葉を入れる方法は、デザインをそのまま保ち、加えるのは言葉と、多くのサイトにすでにある見えなくするためのクラスだけなので、ほかにCSSを変えられないテーマで描かれた表に向いています。チェックと同じく、言葉は印刷されません。alt のある画像にする方法も見た目を保ち、印刷もされますが、表を描くすべてのテンプレートで、スタイルシートのアイコンを画像に置き換える必要があります。セルに言葉を書く方法はデザインが変わり、見える人が記号を読み解かずに答えを読めるのはこの方法だけです。表の幅が広がるため、スマートフォンの幅で確認してください。テンプレート、スタイルシート、デザインのどれを変えられるかで選びます。

## 実装の判断

**アイコンフォント。** アイコンフォントは、アイコンを `::before` で入れる文字として描き、その多くはUnicodeの私用領域の文字です。生成された内容はスクリーンリーダーに読まれることがありますが、こうした文字は何も読まれないか、意味のない文字として読まれ、同じように意味が失われます。その文字は `aria-hidden="true"` の要素に入れ、セルやラベルには、上の修正のように見えない言葉か見える言葉を持たせます。

**`content` に書いた言葉。** 商品カードの `content: "New"` のように、CSSだけに書いたラベルは、対応が進み、スクリーンリーダーで読まれることも増えましたが、選択、コピー、ページ内検索ができず、スタイルシートの変更で失われやすくなります。情報を伝える言葉はHTMLに置きます。

**ロゴとリストの行頭記号。** 空のリンクの背景にロゴを置くと、リンクに名前がなくなります。これは[名前のないバナーや画像のリンク](/ja/learn/empty-links)で扱う問題です。行頭記号が意味のある画像になっているリスト（完了はチェック、未着手はバツなど）も、表と同じように意味が失われます。各項目に言葉で書きます。

**セルの `aria-label`。** `td` に `aria-label` で名前を付けるのは近道に見えますが、セルの `aria-label` を読むかどうかはスクリーンリーダーによって違い、スタイルを無効にすると、やはり言葉はどこにも表示されません。セルの中のテキストなら、どこでも伝わります。

**飾りの背景は問題ない。** ヒーローの後ろの写真、質感、区切り線は何も伝えないため、CSSに置くのが適切です。画像がなくなったときに何かが失われるかどうかで判断します。

## 修正の確認

1. スクリーンリーダーで、表を移動するキーを使って各プランの列を下へ移動する。どのセルも「含まれる」か「含まれない」と読まれ、「空白」と読まれるものがないことを確認する。
2. ブラウザーのアクセシビリティツリーで、すべてのデータセルに名前かテキスト（「含まれる」「含まれない」、またはその名前の画像）があることを確認する。
3. CSSを無効にするか、ページをリーダー表示で開く。各セルが、プランに機能が含まれるかどうかを伝えていることを確認する。
4. プランを知る人に、各セルの言葉と元のチェックやダッシュを見比べてもらい、合っていることを確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。料金表のテンプレートやスタイルシートを変更した際は、再度確認します。

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。パス、クラス名、プランは仮のものです。表のセルの読み方はスクリーンリーダーによって少しずつ違うため、利用者が使う支援技術で確認してください。SVGや img のアイコンを同じように使う場合も、同じ決まりに従います。[名前がない、または読まれてしまうSVGアイコン](/ja/learn/svg-icons)と[意味ではなく名前で読み上げられる絵文字](/ja/learn/emoji)を参照してください。実際に使用する支援技術で、操作全体を確認してください。

---

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