mcpField
A
aria-live-regions

aria-live-regions

github.com/mgiffordデザイン・UI57160 回インストール6 週間前に更新
リポジトリ

aria-live-regions について

aria-live-regionsは、動的な更新を含む画面を作る開発者向けのアクセシビリティスキルです。可視コンテンツやフォーカス管理の代わりにせず、適切な通知方法とタイミングを選ぶ方法を説明します。

主な機能

  1. 01status、alert、logの用途を区別
  2. 02aria-liveと関連属性を解説
  3. 03通知のタイミングと範囲を案内
  4. 04検索、フォーム、進捗の例を掲載

活用シーン

  1. 01保存や検索結果の状態メッセージ追加
  2. 02動的なフォームエラーや進捗通知の設計

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mgifford/accessibility-skills --skill aria-live-regions

インストール後、Claude Code で/aria-live-regionsを入力して読み込まれたか確認します。

同梱ファイルREADME.mdSYNC.md

詳細

ARIA Live Regions アクセシビリティスキル

正規のソース:examples/ARIA_LIVE_REGIONS_BEST_PRACTICES.md(mgifford/ACCESSIBILITY.md内) このスキルはそのファイルをもとに作成されています。迷った場合は、サンプルを正としてください。

支援技術に通知される動的なコンテンツ更新を実装する際は、以下のルールを適用してください。 プロジェクトに動的なコンテンツ更新が含まれる場合にのみ、このスキルを読み込んでください。


基本原則

ライブリージョンを使うと、キーボードフォーカスを移動せずに、動的なステータス情報をスクリーンリーダーのユーザーに伝えられます。結果件数、保存完了の確認、進捗情報など、簡潔な更新に役立ちます。ライブリージョンは汎用的な通知システムではありません。 表示されるコンテンツ、ネイティブ HTML、フォーカス管理、コントロールのプログラム上の状態の代わりにはなりません。タイミングが悪かったり、通知が過剰だったりすると、見逃されたり、重複したり、順序が入れ替わったり、操作を妨げたりすることがあります。

表示されるステータスメッセージを優先してください。拡大表示を使うユーザー、認知や学習に障害のあるユーザー、ページ上の別の場所で起きた変化に気づかないユーザーにも役立ちます。ライブリージョンを使う前に、ネイティブ HTML とプログラム上のコントロールの状態(例:aria-expanded)を使用してください。


重大度の尺度(このスキル)

レベル意味
Critical動的なコンテンツの変更が支援技術に情報を伝えず、ユーザーがタスクを完了できない
Serious緊急性のない更新に assertive/role="alert" を使用して文の途中で割り込む。または、伝えるべき更新より前にライブリージョンが公開されていない
Moderateステータス更新がない。成功/失敗が通知されない。通知が冗長すぎる
Minor通知が重複する。ライブリージョンのコンテンツが表示テキストと不必要に重複する

WCAG 4.1.3 が実際に求めるもの

WCAG 4.1.3 は、既存のステータスメッセージをプログラムで判別できるようにし、フォーカスを移動せずに支援技術が提示できることを求めています。動的な変更のたびに新しいステータスメッセージを作成することは求めていません。更新された結果リストはコンテンツであり、「18件の結果が見つかりました」のようなメッセージがステータスメッセージです。ダイアログにフォーカスが移るのはコンテキストの変化であり、ステータスメッセージではありません。

既定の反射的な対応ではなく、状況に応じて仕組みを選んでください。

状況主な仕組み
ボタンでコンテンツを展開/折りたたむボタンにフォーカスを維持し、aria-expanded を更新する
タブでパネルを切り替えるタブのパターンと、その選択状態を使用する
モーダルダイアログを開くダイアログ内にフォーカスを移動する
SPA でルートを変更するドキュメントタイトルを更新し、論理的な見出しにフォーカスを移動する
フォームで複数のエラーを表示するエラーの要約とフォーカス管理を使用し、各エラーを対応するフィールドに関連付ける
保存/カート/フィルター/検索が完了する簡潔で表示される role="status" メッセージ
重要で時間制約のある状況が発生する表示される role="alert" メッセージ
状況に対して即時の判断が必要になるアラートダイアログを表示し、ダイアログ内にフォーカスを移動する
順序付きの履歴に新しい項目が追加されるセマンティクスが適合する場合は role="log" を使用する

ライブリージョンはフォーカス管理を補完できますが、同じ情報をすでに伝えているフォーカスの変更を通知するために使うべきではありません。


重要:role="status"、role="alert"、role="log"

ロール暗黙の aria-live暗黙の aria-atomic用途
role="status"politetrueすぐに対応する必要のない参考情報
role="alert"assertivetrue重要で、通常は時間的制約のある情報
role="log"polite—新しい情報が追加される順序付きの記録(チャット、アクティビティログ)
<p id="cart-status" role="status"></p>
<p id="connection-alert" role="alert"></p>

互換性に関する検証済みの必要性がない限り、これらのロールに冗長な aria-live/aria-atomic の値を明示的に追加しないでください。アラートにフォーカスは移動せず、閉じるために応答を求めるべきではありません。ユーザーが作業を中断して応答する必要がある場合は、代わりに完全なモーダルダイアログのキーボード操作を備えた ARIA APG のアラートダイアログパターン を使用してください。通常の入力値検証、成功の確認、またはすべてのフィールドエラーに role="alert" を使用しないでください。

<div role="log" aria-relevant="additions" aria-atomic="false">
  <p>Jordan uploaded quarterly-report.pdf.</p>
</div>

role="log" には、見出しと aria-labelledby によるアクセシブルな名前が必要です。また、各エントリーは単独で理解できるようにしてください。

aria-live の単独の値(ライブリージョンのロールが適さない場合にのみ使用してください):

値意味
offデフォルト — フォーカスがリージョン内にない限り、更新内容は提示されません
polite次の適切なタイミングで提示します
assertive最優先 — 割り込みが不可欠な場合にのみ使用します

assertive は優先度を示しますが、特定の AT が音声を直ちに中断することを保証するものではありません。実際の動作は製品、設定、ほかの出力待ちの内容によって異なります。緊急性のない更新に assertive/role="alert" を使用するのは重大な問題です。


重大:aria-atomic、aria-relevant、aria-busy

aria-atomic(デフォルトは false):フレーズ全体が必要な文脈を伝える場合(「カートに3個の商品があります」)は true を使用します。追加内容ごとに独立して意味が通じる場合(新しいチャットログエントリー)は false を使用します。status/alert のデフォルトはすでに true です。true の場合、リージョンのアクセシブルな名前/ラベルも提示されることがあります。変更されたテキストノードだけでなく、結果として伝えられるフレーズ全体をテストしてください。

<div role="status" aria-atomic="true" id="filter-status">
  Showing 24 of 156 results for "accessible forms"
</div>

aria-relevant — デフォルトは additions だけではなく additions text です。値は additions、removals、text、all です。これらは AT への提案であり、通知を保証するものではありません。removals/all は控えめに使用してください。重要な情報の通知を削除イベントに頼らないでください。

aria-busy — 関連する更新をまとめてリージョンに反映する間は true に設定し、完了したら false に戻してください。AT はリージョンがビジーでなくなるまで処理を保留する場合があります。

results.setAttribute('aria-busy', 'true');
renderResults(items);
results.setAttribute('aria-busy', 'false');

エラー発生後も含め、必ず aria-busy をリセットしてください — 例外の後に true のままにするのは、よくある失敗です。読み込み状態を示す唯一の方法として aria-busy を使用しないでください。視覚的なステータス表示も用意してください。


重要:更新タイミング — 固定遅延による回避策は使わない

ライブリージョンの動作は、関連するコンテンツが変更される前にアクセシビリティ API がそのリージョンを認識しているかどうかに左右されます。ライブリージョンと完了メッセージを同じ DOM 操作で追加すると、認識されないことがあります。重要なルールは、まず安定したライブリージョンを公開し、その後のイベントに応じてコンテンツを更新することです。 リージョンはページの初回読み込み時から存在している必要はありません。コンポーネントや SPA では、後から更新が行われる前にマウントされていれば問題ありません。初回読み込み時にユーザーが情報を理解する必要がある場合は、ライブリージョンに頼らず、読み上げ順序の中で見えるように配置してください。

ライブリージョンに対して、50ms、100ms、またはその他の固定遅延が信頼できると定める標準はありません。 タイムアウト後にリージョンを空にして同じテキストを再挿入すると、出力が見落とされたり、重複したり、古くなったり、順序が入れ替わったりすることがあります。このパターンは使わないでください。 代わりに、以下の方法を使ってください。

  • 安定したアナウンス用ノードを維持する
  • コンポーネントのマウント後に更新する
  • フレームワークで状態からメッセージをレンダリングする
  • 急速な一連の更新では、最終的な意味のある結果だけを通知する
  • 古くなった非同期リクエストからの応答を抑制する
  • 多数のコンポーネントが状態を通知する場合は、テスト済みのアプリケーション用アナウンサーの抽象化を使う

多くの実装では role="alert" に特別な処理が適用されるため、動的にレンダリングされたアラートのコンテンツは通知されることがよくあります。ただし、これは alert が新しい要素に aria-live="assertive" を追加するのと同等であることを意味するものではなく、ページの初回読み込み時のアラートが確実に通知されることを意味するものでもありません。


重大:表示状態とコンテンツの範囲

視覚的に隠されたライブリージョンは、同等の情報がすでに視覚的に表示されており、非視覚的な補足が本当に必要な場合、または複雑な視覚的更新を説明する簡潔な通知が必要な場合にのみ使用してください。同じメッセージを、表示されるライブリージョンと別の隠されたライブリージョンの両方に重複して入れないでください。通知が繰り返される原因になります。

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

通知が期待されている間、ライブリージョンに hidden、display: none、visibility: hidden、または aria-hidden="true" を絶対に適用しないでください。次の更新まで空のリージョンを公開したままにする必要がある場合は、.status:empty { display: none; } のようなルールを避けてください。

ライブリージョンの範囲は狭く保ち、結果コンテナー、フォーム、テーブル、ページセクション全体ではなく、短いステータスを通知してください。ライブリージョンの入れ子を避け、別のライブリージョン内に role="alert" を配置しないでください。通常、インタラクティブなコントロールはライブリージョン内に配置しません。トーストにアクションが含まれる場合は、見つけて操作できるよう十分な時間表示し続け、独自のフォーカス戦略を設けてください。


実装パターン

アクション後に表示するステータス:

<button type="button" id="add-to-cart">Add notebook to cart</button>
<p id="cart-status" role="status"></p>
addButton.addEventListener('click', () => {
  addProduct('Notebook');
  cartStatus.textContent = 'Notebook added to cart. 3 items in cart.';
});

古くなったリクエストを処理する検索結果:

let requestSequence = 0;
searchForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  const sequence = ++requestSequence;
  results.setAttribute('aria-busy', 'true');
  resultStatus.textContent = `Searching for ${query}.`;
  try {
    const items = await searchProducts(query);
    if (sequence !== requestSequence) return; // stale response, ignore
    renderSearchResults(results, items);
    resultStatus.textContent = `${items.length} results found for ${query}.`;
  } catch (error) {
    if (sequence === requestSequence) {
      resultStatus.textContent = 'Search could not be completed. Try again.';
    }
  } finally {
    if (sequence === requestSequence) results.setAttribute('aria-busy', 'false');
  }
});

信頼できない結果データは安全にエスケープしてレンダリングしてください。処理が速い場合に読み込みと完了の両方が立て続けに通知されないよう、表示される「検索中」メッセージを遅らせることを検討してください。

文字数:キー入力ごとではなく、しきい値に達したときだけ通知する:

const thresholds = new Set([100, 50, 20, 10, 0]);
let lastAnnounced = null;
summary.addEventListener('input', () => {
  const remaining = summary.maxLength - summary.value.length;
  remainingCount.textContent = remaining; // visible count updates continuously
  if (thresholds.has(remaining) && remaining !== lastAnnounced) {
    countStatus.textContent = `${remaining} characters remaining.`; // hidden status, threshold only
    lastAnnounced = remaining;
  }
});

フォームエラー: 視覚的に表示する要約を示し、フォーカスをそこに移動してください。これはフォーカス管理のパターンであり、要約自体にライブリージョンは必要ありません。ユーザーの入力中に、各フィールドのエラーすべてに role="alert" を追加しないでください。フォーカスですでに提示された内容と重複しない場合は、近くに簡潔な role="status" を置くことで、単一の非同期フィールドチェックに適することがあります。

進行状況: ネイティブの <progress> を使用してください。意味のある節目や完了を通知し、パーセンテージが変わるたびには通知しないでください。

応答を必要とするセッションタイムアウト: アラートダイアログ(role="alertdialog"、aria-modal="true"、アクセシブルな名前と説明、内部への初期フォーカス、キーボード操作の閉じ込め、閉じた後にフォーカスを戻す)を使用してください。アサーティブな通知だけでは、時間制限のあるインタラクションをアクセシブルにすることはできません。タイムアウト自体には WCAG 2.2.1「タイミング調整可能」に従ってください。


中程度:複数の更新の管理

ライブリージョンが頻繁に更新されると、読み上げキューがあふれ、ユーザーが必要とする結果が伝わらなくなることがあります。

  • バックグラウンドの動作よりも、ユーザーが明示的に行った操作の結果を先に通知します
  • 入力によって起動するフィルターや検索の通知は、デバウンスします
  • 関連する更新を、役立つ一文にまとめます
  • 古くなった非同期レスポンスをキャンセルするか、無視します
  • スクロール、ポインターの移動、アニメーションフレーム、時計の刻みは通知しません
  • 処理が実質的にすぐ完了する場合は、読み込み状態を通知しないようにします
  • 継続的なストリーム(チャット、スポーツ、モニタリング)には、一時停止/ミュートのコントロールを用意します
  • 同じイベントに対して status と alert のメッセージを同時に通知しないようにします

ロールがすでに文脈を示している場合は、「通知」や「ステータス」のような接頭辞を避けます。


フレームワークとコンポーネントに関するガイダンス

アクセシビリティを決定するのは、実行時に生成される DOM とアクセシビリティツリーであり、フレームワークではありません。多くのコンポーネントで使う場合は、アプリケーションのルート近くに安定した通知領域を配置します。通知内容は、フレームワークが管理するノードを直接変更するのではなく、フレームワークの状態を通じてレンダリングします。後続の状態変更で通知領域が更新される前に、通知領域がマウントされていることを確認します。ネストされたルートやポータルによって、アクティブな通知領域が重複して生成されないようにします。ハイドレーション、ルート遷移、再マウントは、通知領域がリセットされる可能性があるタイミングの境界として扱います。中間のテキストが通知されないよう、状態の更新をまとめます。

React のパターン(最初にマウントし、エフェクトで更新します。状態変更前に通知領域がマウント済みであれば、固定の遅延は不要です):

const [status, setStatus] = useState('');

useEffect(() => {
  // Runs after the region has mounted and rendered; no arbitrary timeout needed
}, [status]);

return (
  <>
    <div id="status-region" role="status" aria-atomic="true" className="visually-hidden">
      {status}
    </div>
  </>
);

フレームワークのユーティリティを使って、優先順位、重複排除、古くなったリクエストの処理を調整できます。ただし、サポート対象の支援技術を使った手動テストは引き続き必要です。


テスト

静的検査: 選択したロールがメッセージの目的に合っていること、暗黙のロールのプロパティが明示的なプロパティと矛盾していないこと、通知領域が報告すべき更新の前に公開されていること、更新中に通知領域がアクセシビリティツリーから隠されていないこと、ステータステキストが短く、大きく変化するコンテナーを囲んでいないこと、ライブリージョンがネストされていないこと、重複した通知領域がマウントされていないこと、成功時と失敗時の両方で aria-busy が false に戻ること。

手動での操作テスト(重要なフローごと):更新箇所から離れた場所でコンテンツの読み上げを開始します。キーボードで操作を開始します。メッセージが表示されるべきときに表示されることを確認します。ポライトなメッセージが不必要に読み上げを中断しないことを確認します。アラートが重要な状況に限って使われていることを確認します。更新を素早く発生させ、役立つ結果だけが提示されることを確認します。同じ操作を繰り返し、2回目の結果も通知されることを確認します。リクエストを重ねて発生させ、古くなった結果が通知されないことを確認します。ステータスの更新によってフォーカスが移動せず、論理的な位置に保たれることを確認します。

自動チェックでは、無効な ARIA 値、明示的/暗黙的なプロパティの競合、aria-hidden によって隠されたライブリージョン、ネストされたライブリージョン、重複した ID、テスト完了後も aria-busy="true" のままになっているコンポーネントを検出できます。ユーザーの操作後の最終的な DOM メッセージを検証する統合テストを追加し、エラーと古くなったリクエストの経路も含めてください。DOM の検証に合格しても、支援技術で正しく通知されたことの証明にはなりません。


よくある失敗

失敗修正方法
新しいライブリージョン要素とそのメッセージを一度の操作で追加する安定したリージョンを公開し、その後のイベントで更新します
初期ページ読み込み時にリージョンが存在することを必須とするマウント済みコンポーネントを含め、該当する更新の前にリージョンが公開されていることを必須とします
固定時間(例:50ms)の経過後にテキストをクリアして再挿入する一律のタイムアウトをなくし、安定した状態を更新して、繰り返し発生する結果をテストします
通常の確認に assertive を使用するrole="status" を使用します
assertive が即時の割り込みを保証すると期待する優先度として扱い、実際にサポートされている組み合わせをテストします
すべてのフィールドエラーに role="alert" を設定するエラーサマリー、フィールドとの関連付け、フォーカス管理を使用します
アコーディオンが開いたときにコンテンツへフォーカスを移動する開閉コントロールにフォーカスを維持し、aria-expanded を更新します
結果リスト全体を読み上げる簡潔な結果サマリーを読み上げます
空のリージョンを display: none で非表示にする後の更新を読み上げる必要がある場合は、安定したリージョンを公開したままにします
すべての文字、パーセンテージ、秒数を読み上げる意味のあるしきい値と完了を読み上げます
例外発生後も aria-busy="true" のままにするすべての完了経路でリセットします
表示メッセージと非表示のライブメッセージを重複させる1つのステータスメッセージを使用するか、重複しないことをテストした設計を使用します

完了条件チェックリスト

  • 各メッセージを、コントロールの状態、ステータス、アラート、ログ、ダイアログの内容、またはフォーカスされたコンテンツに分類している
  • 実用的な場合は、表示情報も提供している
  • 案内的な更新には role="status" を使用し、role="alert" は重要な更新または時間的制約のある更新に限定している
  • 応答が必要な場合は、アラートダイアログとフォーカス管理を使用している
  • 該当するコンテンツの更新前にライブリージョンを公開している — 初期ページ読み込み時や固定時間後の読み上げ保証に依存していない
  • 固定時間後にクリアして再挿入する回避策を使用していない
  • aria-atomic、aria-relevant、aria-busy は、その動作が更新内容に合致する場合にのみ使用している。aria-relevant の既定値が additions text であることを理解している
  • 更新中にリージョンをアクセシビリティツリーから非表示にしていない
  • 大きなコンテナーやインタラクティブなコントロールをライブリージョンとして使用していない
  • 頻繁な更新、重複する更新、古くなった更新を減らすか抑制している
  • 読み上げが有効な場合も、読み上げが行われなかった場合も、フォーカス管理が適切に保たれている
  • 成功、エラー、読み込み、同じ操作の繰り返し、リクエストの重複が発生する経路をテストしている
  • テスト済み:NVDA+Chrome、JAWS+Chrome、VoiceOver+Safari(サポート対象に含まれる場合はNVDA+Firefoxも)

主な WCAG 基準

  • 4.1.3 ステータスメッセージ(AA)— ステータス更新が読み上げられない場合は重大
  • 4.1.2 名前、役割、値(A)
  • 3.3.1 エラーの特定(A)
  • 3.3.3 エラー修正の提案(AA)
  • 2.4.3 フォーカス順序(A)
  • 2.2.1 タイミング調整可能(A)

参考資料

標準の展望: WCAG 3.0 は策定中です。ステータスメッセージの要件は 維持され、拡張される可能性もあります。 こちらを確認してください:https://www.w3.org/TR/wcag-3.0/

コメント