# 名前のないバナーや画像のリンク

Source: https://easeweb.dev/learn/empty-links
Topics: ボタンとリンク > リンクが行き先を伝える (WCAG 2.4.4)
The fix: バナーのリンクに、ピルの言葉で始まり、リンク先が分かる名前を付けます。空のリンクにその言葉を、視覚的に隠したテキストか aria-labelledby で入れるか、ピルそのものをリンクにして疑似要素でクリック範囲をバナー全体に広げます。どちらでも適合し、前者は変更が小さく、後者は読み上げがより整います。
Test with: keyboard, screen reader, speech input, mouse
References: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F89 https://www.w3.org/WAI/WCAG22/Techniques/html/H30 https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA7 https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#accessibility

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

## 場面

コーヒー焙煎店のトップページは、コーヒー豆の写真に「秋のローストが入荷しました」という見出し、新しい豆の説明、白い「ローストを見る」のピルを載せたプロモーションバナーで始まります。バナーのどこにポインターを置いても指の形になり、ホバーするとピルが塗りつぶされ、どこをクリックしても秋のローストの一覧へ進みます。Tabキーでバナーに移動すると、スクリーンリーダーは「リンク」とだけ読むか、「collections/autumn-roasts」とアドレスを読み上げます。リンク一覧を開くと言葉のない、またはアドレスだけの項目があり、音声操作で「ローストを見るをクリック」と話しても反応がありません。

## 確認できる問題

バナー全体をクリックできるように、テーマはバナーの写真をリンクで包んでバナーいっぱいに広げ、その上に見出し、説明文、ピルを重ねています。写真は装飾なので `alt=""` が付いており、リンクにはほかに何もありません。テキストがないため、アクセシブルな名前もありません。alt がまったくない場合は、画像のファイル名を読むスクリーンリーダーもあり、状況は変わりません。よくある別の作りでも結果は同じです。写真はリンクの外に置いたまま、空の `<a>` を絶対配置でバナーに重ねます。どちらでも、文字はリンクの外の隣の要素にあり、ピルはボタンに見えますが、クリックを下のリンクへ通す `span` にすぎません。それでもブラウザーはこの箱をクリックでき、フォーカスできるものとして扱うため、マウスでは動き、バナーは完成して見えます。

WCAG 2.4.4 リンクの目的（コンテキスト内）は、リンクの目的を、そのテキストだけで、またはソフトウェアが結び付けられる文脈と合わせて分かるようにすることを求めています。文脈とは、リンクを含む文、段落、リスト項目などです。バナーのリンクにはテキストがなく、見出しやピルはコードの上ではリンクの文脈ではありません。リンクを含む要素ではなく、隣の要素の中にあるからです。そのため、スクリーンリーダーは「リンク」かアドレスしか読めず、秋のローストの一覧へ進むリンクだとは分かりません。空のリンクは、周りに何があっても不適合です。言葉はあっても、どのカードにも同じあいまいな言葉が並ぶリンクは別の問題で、[どれも同じ「続きを読む」のリンク](/ja/learn/read-more)で扱っています。

## 利用者への影響

リンクは一覧の中や一つずつなど、前後の文脈なしに読まれることが多いため、どのリンクにもそれ自身の言葉が必要です。このようなバナーはトップページやキャンペーンページの先頭にあり、そのリンクがセールへの主な入口であることもよくあります。

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

axe の `link-name` のような自動チェックは、空のバナーリンクを確実に検出します。ただし名前が役に立つかは判断しないため、「IMG_2041.jpg」という名前のリンクや、alt が「ロゴ」のアイコンは合格になります。AIによる手早い修正では `aria-label="リンク"` や `aria-label="バナー"` を加えることがあり、警告は消えても誰の役にも立たず、音声操作の利用者はピルの言葉を話しても届かないままです。名前が役に立つのは、一覧で読んでもリンク先が分かるときだけです。

## 修正前：装飾として扱った写真だけのリンク

```html
<div class="banner">
  <a href="/collections/autumn-roasts" class="banner-link">
    <img src="/images/autumn-roasts.jpg" alt="">
  </a>
  <div class="banner-text">
    <h2>秋のローストが入荷しました</h2>
    <p>今週焙煎した、3種の新しいシングルオリジン。</p>
    <span class="banner-cta">ローストを見る<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* 写真がバナーいっぱいに広がり、リンクがそれを覆います。 */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 文字は写真の上に載り、クリックは下のリンクへ通します。 */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
```

リンクの中身は `alt=""` の画像だけなので名前がなく、2.4.4を満たしません。リンク先を示すピルは、リンクの一部ではなく、リンクの上に描かれた絵にすぎません。

## 修正前：同じバナーに空のリンクを重ねる

```html
<div class="banner">
  <img src="/images/autumn-roasts.jpg" alt="">
  <a href="/collections/autumn-roasts" class="banner-link"></a>
  <div class="banner-text">
    <h2>秋のローストが入荷しました</h2>
    <p>今週焙煎した、3種の新しいシングルオリジン。</p>
    <span class="banner-cta">ローストを見る<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* 写真がバナーいっぱいに広がり、リンクがそれを覆います。 */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 文字は写真の上に載り、クリックは下のリンクへ通します。 */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
```

違いは写真の位置だけです。写真はリンクの外にあり、リンクはバナーに重ねた空の箱になります。見た目も動きも同じで、同じように2.4.4を満たしません。

## 修正後：今あるリンクにピルの言葉を補う

```html
<div class="banner">
  <a href="/collections/autumn-roasts" class="banner-link">
    <img src="/images/autumn-roasts.jpg" alt="">
    <span class="visually-hidden">ローストを見る</span>
  </a>
  <div class="banner-text">
    <h2>秋のローストが入荷しました</h2>
    <p>今週焙煎した、3種の新しいシングルオリジン。</p>
    <span class="banner-cta">ローストを見る<span aria-hidden="true"> →</span></span>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* 写真がバナーいっぱいに広がり、リンクがそれを覆います。 */
.banner img,
.banner-link {
  position: absolute;
  inset: 0;
}
.banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 文字は写真の上に載り、クリックは下のリンクへ通します。 */
.banner-text {
  position: relative;
  pointer-events: none;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

言葉を補う方法では、レイアウトは変わりません。バナーのリンクは「ローストを見る、リンク」と読み上げられ、ピルの言葉を話せば音声操作でも見つかります。二つ目の作りのバナーでは、同じ `span` を重ねた空のリンクに入れるだけで、ほかは変わりません。写真がリンクの唯一の中身なら、`alt="ローストを見る"` のように、写真の alt に言葉を入れることもできます（達成方法H30）。この alt は写真の内容ではなく、リンク先を表します。名前に見出しではなくピルの言葉を使うのは、押すものとして読まれるのがピルだからです。WCAG 2.5.3「ラベルを含む名前」は、操作要素の名前に、画面上でそのラベルになっている言葉が含まれることを求めています。見えている言葉を話して操作できるようにするためです。隠したテキストの代わりに、ピルに付けた `id`、またはピルと見出しの `id` を `aria-labelledby` で参照して名前を取ることもできます（達成方法ARIA7）。こうすると、言葉がずれません。どちらの場合も、バナーを上から順に読むと、リンクとテキストでピルの言葉が2回読まれます。

## 修正後：ピルをリンクにする

```html
<div class="banner">
  <img src="/images/autumn-roasts.jpg" alt="">
  <div class="banner-text">
    <h2>秋のローストが入荷しました</h2>
    <p>今週焙煎した、3種の新しいシングルオリジン。</p>
    <a href="/collections/autumn-roasts" class="banner-cta">ローストを見る<span aria-hidden="true"> →</span></a>
  </div>
</div>
```

```css
.banner {
  position: relative;
}
/* 写真がバナーいっぱいに広がります。 */
.banner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 文字は写真の上に載り、その箱がバナー全体を占めます。 */
.banner-text {
  position: relative;
}
/* ピルのクリック範囲を文字の箱、つまりバナー全体に広げます。 */
.banner-cta::after {
  content: "";
  position: absolute;
  inset: 0;
}
.banner:hover .banner-cta {
  background: #fff;
  color: #2b1a10;
}
/* フォーカスの枠はバナー全体の外側に描き、写真に埋もれないようにします。 */
.banner-cta:focus-visible {
  outline: none;
}
.banner-cta:focus-visible::after {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
}
```

ピルが本物のリンクになり、疑似要素によってバナー全体も引き続きクリックできます。疑似要素はバナーいっぱいに広がる文字の箱を覆い、文字はもうクリックを下へ通しません。ピルの言葉がそのまま名前になるため、スクリーンリーダーは「ローストを見る、リンク」と読み、音声操作の利用者は見えている言葉を話して操作できます。リンクにするのは見出しではなくピルです。押すものとして読まれ、利用者が口にするのはピルの言葉で、その言葉がリンク先も示しているからです。見出しは見出しのまま残り、見出しで移動する人には一度だけ読まれます。ピルのないバナーなら、代わりに見出しをリンクにします。リンクはバナーに一つで、同じ言葉が2回読まれることもありません。

## どの方法を選ぶか

どちらの方法でもバナーのリンクに名前が付き、2.4.4を満たします。言葉を補う方法は変更が小さく、レイアウトを変えずに済むため、構造を変えられないテーマや外部のスライドショーに向いています。ピルをリンクにする方法は、リンクが一つで読み上げも一度、名前はピルに見えている言葉そのものになるため、より整った形です。テンプレートを変更できる場合はこちらを選びます。自社のバナーはピルをリンクにし、変更できないスライドショーは重ねたリンクに名前を付ける、というように組み合わせても構いません。

## 実装の判断

**広げたリンクが他の操作要素を覆う。** バナー内に2つ目の行動を促すボタンやスライドショーの一時停止ボタンなどがある場合は、`position: relative` と疑似要素より上の `z-index` を指定しないと押せません。バナー内のテキストも選択できなくなります。

**バナー全体を一つの `<a>` で囲む。** HTMLとしては正しく、バナー全体を押せますが、バナー内のすべての文字がリンクの名前になり、見出し、説明文、ピルが一つの長いリンクとして読み上げられます。中に別のリンクやボタンも置けません。ピルをリンクにすれば、どちらの問題も避けられます。

**隠したテキストと `aria-label`。** `display: none` はアクセシブルな名前からも文字を消すため、clip を使った `.visually-hidden` クラスを使います。空のリンクに `aria-label` を付けても名前は付きますが、ブラウザーの翻訳機能で訳されないことがあり、ピルの言葉とずれやすいため、隠したテキストか `aria-labelledby` を優先します。

**写真の上の文字。** 文字が写真の上にあるため、コントラストを確保するには、文字の下に、文字の後ろで最も濃くなる暗い幕が必要です。バナーがとるすべての幅で、どの文字の後ろでも写真の最も明るい部分と比べて確認します。

**画像とアイコンのリンク。** 画像だけのリンクでは、alt に画像の説明ではなくリンク先（「Northside Roasters ホーム」「Instagram」）を書きます。同じリンク内にテキストもある画像は `alt=""` にし、名前が二重に読まれないようにします。SVGアイコンは `aria-hidden="true"` で隠し、リンクに視覚的に隠したテキストを入れます。

**編集できるコンテンツ。** テーマやCMSでは、バナーのピルの文字が任意項目のことがよくあります。空欄のままだと、広げたリンクはまた名前を失います。リンクを設定したらラベルを必須にするか、ラベルが空のときはリンクを出力しないようにします。

## 修正の確認

1. Tabキーでページ内のすべてのリンクを移動し、ブラウザーのアクセシビリティツリーで、それぞれに名前があることを確認する。
2. スクリーンリーダーのリンク一覧（VoiceOverのローター、NVDAとJAWSではInsert + F7）を開き、バナーの項目でリンク先が分かることを確認する。
3. 画像だけ、アイコンだけのリンクの名前が、画像やファイル名ではなくリンク先を示していることを確認する。
4. 200%の拡大表示とタッチ操作も含め、バナーのあちこちを押してリンクが動くこと、バナー内の他の操作要素も押せることを確認する。
5. 音声操作で「クリック」とバナーのピルの言葉を話し、バナーのリンク先へ進むことを確認する。

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

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。コード内のパスとクラス名は仮のものです。コンテンツの編集者が後からバナーのピルの文字を空欄にすることもあるため、公開前の確認にリンクの名前を含めてください。実際に使用する支援技術で、操作全体を確認してください。

---

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