# 選んだ条件も一覧の変化も伝わらない絞り込みリンク

Source: https://easeweb.dev/learn/filter-links
Topics: ボタンとリンク > ページをその場で更新するリンクが、変化を伝える (WCAG 4.1.2)
The fix: 選んだ絞り込みのリンクにaria-current="page"を付け、見た目もこの属性から描きます。そのうえで、その場での更新を残して、最初からページにあるステータス領域に今の表示内容を書き込むか、リンク先のページを読み込ませて、そのタイトルで変化を伝えます。
Test with: screen reader, keyboard, forced colors
References: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/TR/wai-aria-1.2/#aria-current https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/status_role

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

## 場面

あるショップのテーブルウェアのページでは、6点の商品の上に、「すべて」「マグ」「皿」「ボウル」の丸いリンクが横に並んでいます。どれも自分のアドレスを持つ本物のリンクなので、新しいタブで開いたり、人に送ったりできます。ところが、スクリプトがページの読み込みを止めています。「マグ」を押すと、マグの商品を取得して下の一覧を描き替え、アドレスを `?category=mugs` に変え、「マグ」を黒く塗りつぶします。画面が見える人には、塗りつぶされたリンクと、マグ2点に減った一覧が見えます。スクリーンリーダーの利用者は「マグ、リンク」と聞いてEnterキーを押しますが、何も聞こえません。新しいページは読み込まれないので新しいタイトルは読まれず、スクリプトも何も伝えません。リンクへ戻っても、聞こえるのは前と同じ「マグ、リンク」で、ほかのリンクも同じように読まれます。商品を順にたどらない限り、一覧が変わったことも、どの絞り込みが効いているのかも分かりません。

## 確認できる問題

それぞれのリンクは、絞り込みを選ぶためのコントロールです。塗りつぶされたリンクは「この条件が効いている」という状態を示しています。達成基準4.1.2「名前（name）・役割（role）及び値（value）」は、利用者が設定できる状態を支援技術が取得できること、そしてその変化が支援技術に通知されることを求めています。ここでは、その状態を表すのがスタイルシートだけが読む `is-active` クラスなので、押す前も押したあとも、支援技術に伝えられる状態がありません。さらにスクリプトは、本来はページの読み込みが担う、内容とアドレスの入れ替えを引き受けながら、読み込みなら何もしなくても起きる読み上げを省いています。リンクは「どこかへ移動する」と伝えているのに、実際にはその場でページを変え、今どこにいるのかを伝えるものが何も返ってきません。

## 利用者への影響

こうした絞り込みの行は、商品の一覧、記事の一覧、検索結果の上に置かれます。スマートフォンでは横にスクロールする1行になり、変わる一覧は画面の外にあることが多いため、行だけを見ている人には、リンクが塗りつぶされたことしか分かりません。

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

スキャンは、名前とアドレスを持つ4つの正しいリンクと見なし、問題なしとします。リンクを押さないため、ページがその場で変わることに気づかず、`is-active`、`selected`、`current` といったクラス名にも意味を読み取りません。EaseWebの診断は、アドレスが表示中のページで、同じ行のほかのリンクと見た目が違うのに、コードには選択中の目印がないリンクを指摘します。そのうえで、絞り込みリンクの行を一つずつ目視確認に回します。何が読まれるかは、押してみないと分からないからです。AIアシスタントに絞り込みリンクの修正を頼むと、タブやリストボックスのための `aria-selected` や `role="tab"` を付けることがあります。リンクをボタンに変えてしまい、新しいタブで開いたり共有したりするためのアドレスがなくなることもあります。商品の一覧に `aria-live` を付ける例もありますが、押すたびに全商品が読み上げられるか、一覧ごと置き換わると何も読まれなくなります。

## 修正前：その場で描き替え、クラスで目印を付けた絞り込みリンク

```html
<title>マグ | テーブルウェア</title>
…
<ul class="filters">
  <li>
    <a href="/shop">すべて</a>
  </li>
  <li>
    <a href="/shop?category=mugs" class="is-active">マグ</a>
  </li>
  <li>
    <a href="/shop?category=plates">皿</a>
  </li>
  <li>
    <a href="/shop?category=bowls">ボウル</a>
  </li>
</ul>
<ul id="products">…</ul>
```

```css
.filters a {
  border: 1px solid #1d1d1f;
  border-radius: 999px;
  padding: 0.25em 0.9em;
}
.filters a.is-active {
  background: #1d1d1f;
  color: #fff;
}
```

```js
const filters = document.querySelector('.filters');
const products = document.querySelector('#products');

filters.addEventListener('click', async (event) => {
  const link = event.target.closest('a');
  if (!link) return;
  event.preventDefault();
  products.innerHTML = await loadProducts(link.href);
  history.pushState(null, '', link.href);
  filters.querySelector('.is-active').classList.remove('is-active');
  link.classList.add('is-active');
});
```

- `is-active` クラスは「マグ」を塗りつぶすだけです。スクリーンリーダーは、ほかの3つと同じく「マグ、リンク」と読みます。
- スクリプトがページの読み込みを止めて一覧をその場で描き替えるため、押したあとに何も読まれません。新しいページのタイトルも、メッセージもありません。

## 修正後：選んだリンクに目印を付け、変化を伝える

```html
<title>マグ | テーブルウェア</title>
…
<ul class="filters">
  <li>
    <a href="/shop">すべて</a>
  </li>
  <li>
    <a href="/shop?category=mugs" aria-current="page">マグ</a>
  </li>
  <li>
    <a href="/shop?category=plates">皿</a>
  </li>
  <li>
    <a href="/shop?category=bowls">ボウル</a>
  </li>
</ul>
<p id="filter-status" role="status" class="visually-hidden"></p>
<ul id="products">…</ul>
```

```css
.filters a {
  border: 1px solid #1d1d1f;
  border-radius: 999px;
  padding: 0.25em 0.9em;
}
.filters a[aria-current="page"] {
  background: #1d1d1f;
  color: #fff;
}
```

```js
const filters = document.querySelector('.filters');
const products = document.querySelector('#products');
const status = document.querySelector('#filter-status');

filters.addEventListener('click', async (event) => {
  const link = event.target.closest('a');
  if (!link) return;
  event.preventDefault();
  if (link.getAttribute('aria-current') === 'page') return;
  products.innerHTML = await loadProducts(link.href);
  history.pushState(null, '', link.href);
  filters.querySelector('[aria-current]').removeAttribute('aria-current');
  link.setAttribute('aria-current', 'page');
  status.textContent = `表示中：${link.textContent}`;
});
```

1. サーバーが返すマークアップで、クラスを `aria-current="page"` に置き換えます。このリンクのアドレスは表示中のページなので、スクリーンリーダーは「マグ、現在のページ、リンク」のように現在のページとして読みます。
2. 塗りつぶしの見た目を `[aria-current="page"]` から描きます。選ばれて見えるリンクと、読み上げで選ばれているリンクが、必ず同じになります。
3. 絞り込みの行の下に、視覚的に隠した空の `role="status"` 領域を置きます。最初からページにあり、描き替えられる一覧の外にあるため、ここへの書き込みは、ブラウザーが変化として伝えます。
4. 新しい一覧がそろったら、押されたリンクへ `aria-current` を移し、領域に「表示中：マグ」と書き込みます。スクリーンリーダーは、読み上げ中の内容を読み終えてからこのメッセージを読み、フォーカスはリンクに残るので、続けて別の絞り込みを試せます。
5. 押されたリンクがすでに現在のものなら何もしません。効いている絞り込みの一覧を、もう一度取得することはありません。

これで修正前の例は直り、4.1.2を満たします。選んだ絞り込みがコードで示され、変化は起きたときに伝えられます。4.1.2が求めるのはこの目印ですが、フォーカスのあるリンクで `aria-current` が変わったことをスクリーンリーダーが読むかどうかは、ここでは確認していないため、メッセージで言葉にして伝えます。

## 修正後：リンク先のページを読み込ませる

```html
<title>マグ | テーブルウェア</title>
…
<ul class="filters">
  <li>
    <a href="/shop">すべて</a>
  </li>
  <li>
    <a href="/shop?category=mugs" aria-current="page">マグ</a>
  </li>
  <li>
    <a href="/shop?category=plates">皿</a>
  </li>
  <li>
    <a href="/shop?category=bowls">ボウル</a>
  </li>
</ul>
<ul id="products">…</ul>
```

```css
.filters a {
  border: 1px solid #1d1d1f;
  border-radius: 999px;
  padding: 0.25em 0.9em;
}
.filters a[aria-current="page"] {
  background: #1d1d1f;
  color: #fff;
}
```

```js
// クリックの処理はなし。絞り込みのリンクは、それぞれのページを読み込みます。
```

1. クリックの処理を削除します。どのリンクも、ふつうのリンクと同じように自分のアドレスを読み込みます。サーバーはもともと、絞り込んだ一覧と、そのページのタイトル「マグ | テーブルウェア」を返しているので、スクリーンリーダーは読み込みのときにこのタイトルを読みます。
2. サーバーが絞り込みごとに返すマークアップで、選ばれたリンクに `aria-current="page"` を付けます。
3. 塗りつぶしの見た目を `[aria-current="page"]` から描きます。

これで修正前の例は直り、4.1.2を満たします。新しいタイトルが今の表示内容を伝え、絞り込みの行まで来ると、選んだリンクが現在のページと読まれます。フォーカスはページの先頭から始まるため、一覧まではTabキーや見出しで移動し直します。

## どの方法を選ぶか

どちらも、選んだ絞り込みを示し、何が変わったかを伝えます。その場での更新は、フォーカスを絞り込みの行に残したまま今の位置を保つので、「マグ」のすぐあとに「皿」を試せます。その代わり、ステータス領域と、目印・メッセージ・一覧をそろえ続ける処理が必要です。ページの読み込みは、更新のためのスクリプトも、そろえ続けるものも要りません。ブラウザーとスクリーンリーダーが、ほかのリンクと同じように扱うからです。その代わり、押すたびにページを読み込み、フォーカスとスクロールは先頭に戻ります。絞り込みがあまり押されない場合や、更新のスクリプトが速さのためだけにある場合は、ページを読み込ませます。いくつもの絞り込みを続けて試すページなら、その場での更新を残します。

## 実装の判断

**リンクかボタンか。** 自分のアドレスを持ち、新しいタブで開いたり、ブックマークや共有ができたりする絞り込みはリンクであり、リンクのままにします。アドレスを持たずに一覧だけを変える絞り込みはボタンです。[押された状態が伝わらないトグルボタン](/ja/learn/toggle-buttons)のように `aria-pressed` を付けます。二つの役割の違いは、[リンクと読み上げられ、Spaceキーで押せないボタン](/ja/learn/links-as-buttons)を参照してください。

**aria-currentの値。** `page` を使います。リンクのアドレスは表示中のページで、絞り込みの行はページの集まりだからです。`aria-current="true"` もまとまりの中の現在の項目を示し、スクリーンリーダーは「現在」のように読みます。タブや選択肢、グリッドの行とセルのための `aria-selected` や、行を独自のキー操作を持つタブリストに変えてしまう `role="tab"` は使いません。

**伝えるのは表示内容で、一覧全体ではない。** メッセージは、効いている絞り込みの名前を伝えます。残った商品の数も伝えるなら、押すたびに一度、同じメッセージに件数を入れます。[件数が読み上げられない検索・絞り込み結果](/ja/learn/search-results)を参照してください。商品の一覧に `aria-live` を付けてはいけません。

**領域は描き替える部分の外に置く。** ステータス領域は、ページのレイアウトか、絞り込みのコンポーネントのうちマウントされたままの部分に描きます。一覧の中や、一覧と一緒に置き換えられるブロックの中には置きません。メッセージと一緒に作られた領域は、読まれないことが多いからです。

**並べ替えのリンク。** 「並べ替え：新着順・価格の安い順・価格の高い順」のような行も同じです。今の並び順のリンクに `aria-current="page"` を付け、その場で更新するなら「価格の安い順に並べ替えました」のように伝えます。表の列見出しで並べ替える場合は、見出しに `aria-sort` を付ける別のパターンで、並べ替えできる表のトピックで扱います。

**移動を止めるリンクが守るべきこと。** すべてのクリックで `preventDefault()` を呼ぶ処理は、CtrlキーやCmdキーを押しながらのクリックも止めてしまい、絞り込みを新しいタブで開けなくなります。修飾キー付きのクリックはブラウザーに任せます。また、`popstate` でも一覧を描き替え、「戻る」で前の絞り込みに戻ったとき、目印もそれに合わせて移します。どちらも4.1.2の不適合ではありませんが、リンクがリンクらしく動かなくなったときに失われるものです。

**コントラスト テーマ。** Windowsのコントラスト テーマでは、黒い塗りつぶしがページの背景色に置き換わり、選んだリンクがほかと同じに見えます。太い枠線など、テーマでも残る目印を加えるか、`@media (forced-colors: active)` の中で `Highlight` と `HighlightText` で塗ります。[ハイコントラストモードで消えるフォーカス・アイコン・選択状態](/ja/learn/forced-colors)を参照してください。

## 修正の確認

1. DOMを確認する。行の中で `aria-current="page"` を持つリンクは、表示中のアドレスのリンク一つだけで、塗りつぶしはこの属性から描かれている。その場で更新する場合は、`role="status"` の領域が最初に押す前から空のままページにあり、描き替えられる一覧の外にある。
2. スクリーンリーダーで、絞り込みの行をTabキーで移動する。選んだ絞り込みは現在のページと読まれ、ほかのリンクはそう読まれない。
3. Enterキーで別の絞り込みを押す。その場での更新なら「表示中：」と絞り込みの名前が一度読まれ、フォーカスはリンクに残る。ページを読み込む場合は、新しいタイトルが読まれる。
4. 行へ戻る。新しい絞り込みが現在のページと読まれ、前のものはもうそう読まれない。現在の絞り込みをもう一度押しても何も変わらない。
5. Windowsのコントラスト テーマをオンにする。選んだ絞り込みが、ほかと見分けられる。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。絞り込みのコンポーネント、一覧のテンプレート、ページのレイアウトを変更した際は、再度確認します。

## 制限

ライブリージョンの読み上げはスクリーンリーダーやブラウザーによって異なり、続けて押すと、politeのメッセージが読まれないこともあります。このガイドは、一つずつ条件を選ぶリンクが1行に並ぶ場合を扱います。いくつも同時にチェックして組み合わせる絞り込みは、多くの場合チェックボックスで作られ、その結果の伝え方は[件数が読み上げられない検索・絞り込み結果](/ja/learn/search-results)で扱います。シングルページアプリケーションの画面全体を入れ替えるリンクは[読み上げられないSPAの画面遷移](/ja/learn/route-changes)で、サイトのメニューの現在のページは[現在のページを見た目だけで示すナビゲーション](/ja/learn/current-page)で扱います。実際の絞り込みを、並べ替えやページ送りも含めて、利用者が使うスクリーンリーダーで確認してください。

## スクリーンリーダーでの動き（EaseWebが実機で確認）

`aria-current="page"`: メニューやパンくずリスト、ページ送りで、今いるページへのリンクだと示します。

| スクリーンリーダー | ブラウザーとOS | Tabで移動 | 下矢印キー |
| --- | --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 | 未確認 |
| TalkBack | Chrome, Android | 未確認 | 未確認 |

`role="status"`: polite のライブリージョンです。変化は、スクリーンリーダーが読み終えるまで待ちます。

| スクリーンリーダー | ブラウザーとOS | 下矢印キー | 連続読み上げ |
| --- | --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 | 未確認 |
| TalkBack | Chrome, Android | 未確認 | 未確認 |

https://easeweb.dev/ja/learn/screen-reader-support

---

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