# 定型の通知でもVoiceOverの読み上げを遮るアラート

Source: https://easeweb.dev/learn/urgent-alerts
Topics: ライブ更新 > 緊急のメッセージだけが割り込み、通常の更新は順番を待つ (WCAG 4.1.3)
The fix: 定型の通知は、スクリーンリーダーが今の読み上げを終えるまで待つ role="status" の領域に書き込みます。接続が切れたときなど、待てない少数のメッセージには、別に role="alert" の領域を用意します。どちらの領域も最初から空のままページに置き、フォーカスは移しません。
Test with: screen reader, keyboard
References: https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22 https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA19 https://www.w3.org/TR/wai-aria-1.2/#aria-live https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/alert_role https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-live https://webaim.org/projects/screenreadersurvey10/

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

## 場面

チームの計画アプリは、タスクを1件ずつ表示します。タイトル、期日、やることを書いた数行の説明です。ページの上端には、細い通知バーがあります。同じタスクを同僚が開くと「Samさんがこのタスクを開きました」、期日が動くと「Samさんが期日を5月14日に変更しました」、コメントが届くと「Alexさんから新しいコメント」と出ます。接続が切れると「接続が切れました。これから行う変更は保存されません。」です。スクリプトはこれらをすべて同じ要素に書き込み、その要素には `role="alert"` が付いています。

iPadでVoiceOverを使う利用者が、タスクの説明を1行ずつ読んでいます。最初の行の途中で読み上げが止まり、「Samさんがこのタスクを開きました」と聞こえます。行に戻って読み直すと、少し進んだところで「Samさんが期日を5月14日に変更しました」。次はコメントです。一つひとつの通知は短く、害もありません。それでも、どの通知も読んでいた文を途中で断ち切ります。本当に接続が切れたとき、その警告は、それまでの3つの通知とまったく同じように聞こえます。

ノートパソコンでJAWSやナレーターを使う利用者には、違って聞こえます。各行が最後まで読まれ、そのあとに、読んでいる間に届いた通知が読まれます。途中で切れることはありませんが、alertの役割も何の違いも生みません。「接続が切れました」も「Samさんがこのタスクを開きました」と同じように行の終わりを待ち、同じ調子で読まれます。

## 確認できる問題

`role="alert"` は、assertive のライブリージョンです。ARIAは、assertive の領域の更新を最優先ですぐに伝えるものとし、待っていたメッセージを捨てることも認めています。今すぐ対応が必要なメッセージには欠かせない性質ですが、定型の通知には、あってはならない性質です。達成基準4.1.3は、ステータスメッセージをフォーカスを受け取らずに利用者へ伝えられるようにすることを求めています。そのUnderstandingは、目的を「作業を不必要に妨げない方法で」伝えることだとし、重要でも急ぎでもない内容に `role="alert"` や `aria-live="assertive"` を使うことを失敗例に挙げています。この失敗例の解説ページは、まだ書かれていません。「Samさんがこのタスクを開きました」は、重要でも急ぎでもありません。ただし、スクリーンリーダーがすべて領域の求めどおりに動くわけではありません。2026年のリリースでは、macOSとiPadOSのVoiceOverは、assertive の領域とアラートで読み上げに割り込み、macOSでは読み上げを止めました。Firefoxで試したNVDAも `role="alert"` では割り込み、「alert」という言葉を添えて読んでから、遮った内容を読み直しました。JAWSとナレーターは、アラートを polite の領域と同じように扱い、読んでいる文や行が終わってから読みました。開発者がalertの役割を選んだのは、確実に読み上げられるからです。ただ、その役割が割り込みも起こすことを、ブラウザーは何も警告しません。

## 利用者への影響

通知は、利用者が何もしなくても届きます。同じタスクに関わる人が多いほど、VoiceOverの利用者が遮られる回数も増えます。スマートフォンやタブレットでは、スクリーンリーダーの利用者の多くがVoiceOverを使っています。WebAIMの2024年の調査では、モバイルの回答者の70.6%がVoiceOverを使い、パソコンで主に使うスクリーンリーダーにVoiceOverを挙げたのは9.7%でした。画面が見える人は、バーをちらりと見るか、見ないまま読み続けます。

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

スキャンはライブリージョンを見つけ、最初からページにあることを確かめて合格にします。メッセージが急ぎかどうかは意味の問題で、どのルールにも読み取れません。この診断は、フォームやダイアログの外でメッセージを待っている空の `role="alert"` や `aria-live="assertive"` の領域を、人が中身を確認する対象として挙げます（IDやクラス名からエラー用と分かるものは除きます）。ただし、アプリが実際に送るまで、メッセージそのものは見えません。通知をアクセシブルにするよう頼まれたAIアシスタントも、開発者と同じ理由で、すべてに `role="alert"` を付けることがあります。通知が届く中で聞いてみて、初めて割り込みに気づきます。

## 修正前：すべての通知を一つのアラート領域に

```html
<div id="notices" class="notices" role="alert"></div>
```

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

function notify(text) {
  notices.textContent = text;
}

presence.on('open', (name) => notify(`${name}さんがこのタスクを開きました`));
task.on('due', (change) => notify(`${change.by}さんが期日を${change.due}に変更しました`));
comments.on('new', (comment) => notify(`${comment.author}さんから新しいコメント`));
window.addEventListener('offline', () => notify('接続が切れました。これから行う変更は保存されません。'));
```

領域は最初からページにあるため、通知はすべて読み上げられます。そして、すべてがアラートとして読み上げられます。VoiceOverとFirefoxのNVDAは、「接続が切れました」のときと同じように、「Samさんがこのタスクを開きました」でも文を途中で切ります。JAWSとナレーターでは、二つは同じように聞こえます。

## 修正後：定型の通知はステータス領域、急ぎの通知はアラート領域

```html
<div class="notices">
  <p id="notice-status" role="status"></p>
  <p id="notice-alert" role="alert"></p>
</div>
```

```js
const statusRegion = document.querySelector('#notice-status');
const alertRegion = document.querySelector('#notice-alert');

function notify(text, { urgent = false } = {}) {
  (urgent ? alertRegion : statusRegion).textContent = text;
}

presence.on('open', (name) => notify(`${name}さんがこのタスクを開きました`));
task.on('due', (change) => notify(`${change.by}さんが期日を${change.due}に変更しました`));
comments.on('new', (comment) => notify(`${comment.author}さんから新しいコメント`));
window.addEventListener('offline', () => notify('接続が切れました。これから行う変更は保存されません。', { urgent: true }));
```

定型の通知は、ステータス領域に送られるようになりました。`role="status"` は polite のライブリージョンです。スクリーンリーダーは読んでいる行を読み終えてから、「Samさんがこのタスクを開きました」と読みます。待てないメッセージだけは、これまでどおりアラート領域に送られ、VoiceOverとNVDAでは割り込んで読まれます。割り込むのがそれだけになったため、かえって目立ちます。JAWSとナレーターでは読まれるタイミングが変わらないので、急ぎであることは警告の言葉そのものが伝えます。最初に「接続が切れました」と言い、そのあとで変更への影響を伝えます。どちらの領域も最初から空のままHTMLにあり、通知バーの見た目は変わりません。メッセージの種類は、メッセージを送る呼び出し側で、その場で一度だけ決めます。

## 実装の判断

**急ぎのメッセージとは。** 利用者が何かを失いそうなとき、または今すぐ対応が必要なときです。接続が切れた、保存や支払いに失敗した、セッションが切れそう、作業を止めるエラーが出た、などです。それ以外は定型の通知です。保存した、追加した、更新された、誰かが参加した、コメントが届いた、などです。迷ったら定型の通知として扱います。急ぎでも誰も対応できないメッセージなら、そもそも伝える必要があるかを考えます。

**急ぎであることは言葉で伝えます。** JAWSとナレーターは、アラートをステータスメッセージと同じように読むため、急ぎのメッセージは言葉で急ぎだと分かるようにします。最初の数語で何が起きたかを言い（「接続が切れました」「支払いに失敗しました」）、そのあとで何を意味し、何をすればよいかを伝えます。次の通知と一緒に消えないよう、事実でなくなるまで画面に残し、あとでバーに来た人も見つけられるようにします。

**逆の失敗。** 急ぎのメッセージを polite の領域に入れると、スクリーンリーダーが話し終えるまで待ち、ほかの通知のあとに回ることもあります。よくあるのは、セッションが切れる前の警告です。警告と延長の方法は、[警告も延長もないセッションタイムアウト](/ja/learn/time-limits)で扱っています。

**領域は切り替えず、二つ置きます。** スクリプトがメッセージのたびに `aria-live` を変える一つの領域ではなく、ステータス領域とアラート領域を並べて置きます。二つの領域が固定されていれば、直前のメッセージ用に設定した緊急度で次のメッセージが送られることはありません。

**続けて届く通知。** 領域に書き込むと、前の内容は置き換わります。定型の通知がすぐあとにもう一つ届くと、読まれる前に置き換わることがあります。両方が大事なら、一つのメッセージにまとめるか（「Samさんがこのタスクを開き、期日を5月14日に変更しました」）、直近のいくつかをバーに行として残します。複数のアラートを同時に出すのは、さらに問題です。ARIAは、assertive のメッセージが届いたときに、待っていたメッセージを捨ててもよいとしているため、最後の一つしか聞こえないことがあります。MDNも、複数のアラートを同時に出すことや不要なアラートは、使い勝手を損なうと警告しています。

**通知やトーストのコンポーネント。** 多くは、メッセージの種類ごとに役割を選べます。既定の動きを確認してください。すべてのメッセージをアラートとして表示するものもあります。成功や案内のメッセージはステータスに、今すぐ対応が必要なエラーだけをアラートに割り当てます。トーストを画面に出しておく時間は別の問題で、[読む前や元に戻す前に消えるトースト](/ja/learn/toast-timing)で扱っています。

**事実でなくなったアラートは消します。** 接続が戻ったら、アラート領域を空にし、ステータス領域に「オンラインに戻りました」と書き込みます。警告を残したままにすると、あとでバーを読んだ人に、変更は保存されないという古い内容がまた読まれます。

**修正にならないもの。** 通知へフォーカスを移せば、どのスクリーンリーダーでも読み上げられますが、通知のたびに読んでいた場所から引き離されるため、割り込みより悪くなります。通知をライブリージョンからすべて外すと割り込みはなくなりますが、「接続が切れました」も聞こえなくなります。メッセージと一緒にページへ追加した領域は、まったく読み上げられないことがよくあります。この失敗は[画面に出ても読み上げられないステータスメッセージ](/ja/learn/status-messages)で扱っています。

## 修正の確認

1. 通知が届く前にDOMを確認し、`role="status"` の領域と `role="alert"` の領域があり、空で、`display: none` や `aria-hidden` で隠れていないことを確かめる。
2. VoiceOver、またはFirefoxのNVDAでタスクを1行ずつ読む間に、別の人に、別のブラウザーでタスクを開き、期日を変え、コメントしてもらう。どの通知も行を読み終えてから読まれ、途中で切れる行がない。
3. 開発者ツールでオフラインにする。VoiceOverでは「接続が切れました」がすぐに割り込み、JAWSやナレーターでは行が終わってから読まれる。どれでも、最初に何が起きたかを言い、変更にどう影響するかを伝える。オンラインに戻すと、警告が消える。
4. コードから `role="alert"`、`aria-live="assertive"`、通知コンポーネントの設定を探す。そこに届くメッセージが、どれも今すぐ対応を必要としている。

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

## 制限

ライブリージョンの動きは、スクリーンリーダーやブラウザーによって、またバージョンによっても異なります。このガイドの記述は、2026年時点の各リリースでの動きです。Understanding 4.1.3も、支援技術が利用者の設定に合わせてステータスメッセージを遅らせたり、抑えたり、形を変えたりすることがあると述べています。メッセージが急ぎかどうかは製品についての判断で、そのチームにしかできません。このガイドは、読んでいる間に届く通知を扱っています。読み上げられないメッセージは[画面に出ても読み上げられないステータスメッセージ](/ja/learn/status-messages)で、毎秒読み上げる時計は[スクリーンリーダーが読み上げ続けるカウントダウンタイマー](/ja/learn/countdown-timers)で、確認を求めるダイアログは[質問が読み上げられない確認ダイアログ](/ja/learn/confirmation-dialogs)で扱っています。割り込みそのものを後回しにできるようにすること（レベルAAA）は、別のトピックです。利用者が使うスクリーンリーダーで確認してください。

---

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