プロンプトとコンテキスト
検索、保存、アップロードは、画面遷移やフォーカス移動を伴わずに完了します。視覚的には「保存中」「保存完了」「保存に失敗しました」と表示されますが、この更新はフォーカス中のコントロールの外側で発生することがあります。目標は、入力作業を中断させない短く実行可能なアナウンスを提供し、キーボード、no-script、ネットワーク障害時のフォールバックを用意することです。
面接官がテストするポイント
単一の aria-live 属性を追加するだけではなく、助言的(advisory)なステータス、エラー、緊急のアラートを区別できるか。更新前に live region を構築できるか。重複するアナウンス、競合状態、フォーカス移動を制御できるか。そして、サーバーの結果、再試行アクション、テストを含めて設計できるかを評価します。
明確にすべき質問
- どのアナウンスが助言的なもので、どのアナウンスが次のアクションをブロックするものか?
- ユーザーは並行してリクエストを発行できるか、またレスポンスにはシーケンス番号が含まれているか?
- 失敗時にインラインの再試行、元に戻す(undo)、詳細表示のアクションがあるか?
- 成功後はフォーカスをエディター内に留めるべきか、またエラー時にはどこへ移動させるべきか?
- 通常のフォーム送信、キーボード操作、ズーム、ハイコントラスト(forced colors)モードへの対応は必要か?
30秒での回答
初期 DOM に空の role="status" 領域をレンダリングし、そこへ通常の進捗や完了メッセージを書き込みます。その polite な動作によってフォーカスが奪われないようにします。対処が必要なエラーには、コントロールの近くに可視テキストと復旧経路を提供し、真に緊急なイベントのみ alert を使用します。アナウンスの重複排除とスロットリングを行い、シーケンス番号によって古いレスポンスを破棄し、キーボード、スクリーンリーダー、ネットワーク障害、並行リクエストのテストを行います。
ステップごとの詳細な回答
ステップ 1: 状態を明示的にモデル化する
idle、pending、success、error、cancelled を分けて管理します。ステータス領域には、内部のリクエスト名や1%ごとの進捗ではなく、「保存中」「保存完了」「保存に失敗しました。再試行してください」といった現在の結果を表示します。フィールドエラー、再試行アクション、コントロールとの関連付けは個別に保持します。
ステップ 2: 最初に live region を作成する
W3C および MDN では、コンテンツを変更する前に live region を作成することを推奨しています。role="status" は助言的な情報に適しており、暗黙的に aria-live="polite" を持ちます。更新が発生したときだけ属性を追加するような実装は避けてください。
<p id="save-status" role="status" aria-atomic="true"></p>
<button type="submit">Save</button>ステップ 3: アナウンスの頻度を制御する
同一のテキストを繰り返し書き込んだり、検索中のキー入力ごとにアナウンスしたりしないでください。高頻度な結果にはデバウンスを適用し、「検索結果が更新されました」といった意味のあるサマリーを読み上げます。完了、失敗、必要なアクションでは、次のステップを伝えます。aria-live="assertive" は音声を中断するため、使用は限定的にすべきです。
ステップ 4: 競合とキャンセルを処理する
各リクエストに単調増加するシーケンス番号または AbortController を付与します。マウントされたコンポーネントからの最新のレスポンスのみがステータスを更新できるようにします。キャンセルされたリクエストは失敗アナウンスの対象外とし、新しいリクエストによって以前の保留中メッセージを上書きします。
ステップ 5: フォーカスとエラーを連携させる
通常の成功時にフォーカスを移動させてはならず、ユーザーが操作を継続できるようにします。失敗時には可視テキストをレンダリングし、aria-describedby を使ってコントロールと関連付けます。ページレベルのエラーにはフォーカス可能なサマリーと再試行ボタンが必要です。フォーカスの移動と live アナウンスが重複しないよう、主たる通知チャネルを1つ選択します。
ステップ 6: フォールバック経路を確保する
サーバーは引き続き通常のフォーム送信を受け入れ、構造化された結果を返せるようにすべきです。JavaScript の失敗、タイムアウト、権限切れが発生した場合は、スピナーを表示したままにせず、「保存に失敗しました。もう一度お試しください」のようにステータスとアクションをテキストで示します。ユーザーの入力内容は消去しないでください。
トレードオフと境界条件
status、alert、および可視エラー
status は polite な通知用です。alert は音声を中断するため、すべてのエラーをこれで置き換えるべきではありません。音声だけが情報伝達手段ではないため、可視エラーは引き続き必須です。色、アイコン、アニメーションはテキストを補完するものとして扱います。
進捗の詳細度とノイズ
アップロードのパーセンテージは視覚的に表示しますが、アナウンスするのは開始、意味のあるフェーズ、完了のみにとどめます。live region を1%ごとに更新するとノイズになります。実機や実際のユーザーを用いてスロットリングを調整してください。
ロールアウト計画と検証
コンポーネントと API コントラクト
メッセージテキスト、重複排除、シーケンスチェックを単一のステータスコンポーネントに集約します。API は構造化された success、retryable、fieldErrors フィールドを返し、コンポーネントはサーバーコードをそのまま露出させるのではなく、ユーザー向けの言語にマッピングします。
検証マトリクス
キーボードのみで検索、保存、アップロードを実行し、NVDA や VoiceOver でアナウンスが1回だけ行われることを確認します。低速ネットワーク、タイムアウト、ダブルクリック、順不同のレスポンス、キャンセル、アンロード、ハイコントラストモード、200%ズームを網羅します。DOM セマンティクスを自動化し、その後に読み上げ順序を手動で検証します。
よくある間違いとフォローアップ
間違い: aria-live を動的に追加すること
ノードと属性をあらかじめ配置しておき、そのテキストを更新するようにしてください。そうしないと、一部の支援技術が最初の変更を見逃す可能性があります。
間違い: フォーカスを成功テキストに移動させること
これにより編集中断が発生します。ユーザーがエラーに対処する必要がある場合や結果を確認する必要がある場合を除き、フォーカスを維持してください。
間違い: すべてのエラーを assertive にすること
優先度の高い音声は読み上げを中断します。真に緊急の対応が必要な場合に限定し、目に見える復旧手段を提供してください。
フォローアップ: 古いレスポンスの防止
単調増加シーケンスを比較するか、キャンセル処理とシーケンスチェックを組み合わせます。キャンセル処理だけでは、コールバックが実行されないことを証明できません。
フォローアップ: 有効性の証明
自動スキャン結果だけでなく、スクリーンリーダーでの確認結果、キーボード操作フロー、ネットワーク障害、競合状態の証拠を提示してください。