実践Webアクセシビリティ

実践!アクセシブルなエラーメッセージの表示と通知

Tags: エラーメッセージ, アクセシビリティ, ARIA, JavaScript, 実装

はじめに

ウェブサイトやアプリケーションにおいて、ユーザーが操作に失敗したり、入力内容に不備があったりした場合に表示されるエラーメッセージは、ユーザーエクスペリエンスにとって非常に重要です。エラーメッセージがあることで、ユーザーは何が問題なのかを理解し、適切に対処することができます。

しかし、エラーメッセージが視覚的に表示されるだけでは、すべてのユーザーが必要な情報を取得できるとは限りません。例えば、視覚に障がいのあるユーザーがスクリーンリーダーを利用している場合、エラーメッセージが表示されたことに気づきにくかったり、エラーの内容が適切に伝わらなかったりすることがあります。また、認知に障がいのあるユーザーにとっては、エラーメッセージの内容が理解しにくかったり、どこでエラーが発生したのかが分かりにくかったりする場合もあります。

この記事では、すべてユーザーがエラーを認識し、適切に対処できるようにするための、具体的なアクセシビリティ対応の実装方法を解説します。特に、エラーメッセージの表示、通知、そして関連付けの方法に焦点を当て、コード例と共にステップバイステップで説明します。

エラーメッセージのアクセシビリティが重要な理由

エラーメッセージのアクセシビリティは、WCAG(Web Content Accessibility Guidelines)の複数の達成基準に関連しています。特に以下の点が重要です。

これらの基準を満たすことで、視覚障がい、認知障がい、肢体不自由など、様々なユーザーがエラー情報を正確に理解し、円滑に作業を進めることができるようになります。

具体的な実装方法

アクセシブルなエラーメッセージを実装するためには、以下の3つの要素を考慮する必要があります。

  1. エラーの表示と関連付け: エラーメッセージがどの入力フィールドに関連しているかを明確にし、視覚的・非視覚的に結びつけます。
  2. エラーの通知: エラーが発生したことを、スクリーンリーダーユーザーなどにも動的に伝えます。
  3. エラーの集約(任意): 複数のエラーがある場合に、それらを一覧で表示し、修正を支援します。

1. エラーの表示と関連付け

ユーザーが入力エラーを認識し、修正するためには、エラーメッセージが関連する入力フィールドの近くに表示され、それがエラーメッセージであることが明確である必要があります。

基本的なHTML構造:

HTMLでは、エラーメッセージと入力フィールドをプログラムで関連付けるために、aria-describedby属性を使用することが推奨されます。

<label for="username">ユーザー名 (必須)</label>
<input type="text" id="username" aria-required="true">
<!-- エラーが発生した場合 -->
<div id="username-error" role="alert" style="color: red; display: none;">
  ユーザー名は必須項目です。
</div>

<label for="email">メールアドレス</label>
<input type="email" id="email">
<!-- エラーが発生した場合 -->
<div id="email-error" role="alert" style="color: red; display: none;">
  有効なメールアドレスを入力してください。
</div>

エラーが発生した場合、JavaScriptで該当するエラーメッセージ要素を表示し、入力フィールドにaria-invalid="true"属性とaria-describedby="[エラーメッセージ要素のID]"属性を追加します。

<label for="username">ユーザー名 (必須)</label>
<input type="text" id="username" aria-required="true" aria-invalid="true" aria-describedby="username-error">
<div id="username-error" role="alert" style="color: red;">
  ユーザー名は必須項目です。
</div>

<label for="email">メールアドレス</label>
<input type="email" id="email">
<!-- エラーが発生した場合、こちらはエラーなし -->
<div id="email-error" role="alert" style="color: red; display: none;">
  有効なメールアドレスを入力してください。
</div>

インライン表示: エラーメッセージは、関連する入力フィールドの直下など、視覚的にも関連が分かりやすい位置に表示します。エラーメッセージの周囲に枠線や背景色をつけるなど、視覚的に強調することも有効です。

/* エラー状態の入力フィールドのスタイル */
input[aria-invalid="true"] {
  border-color: red;
  outline-color: red; /* フォーカスリングの色も変更するなど */
}

/* エラーメッセージのスタイル */
.error-message {
  color: red;
  font-size: 0.9em;
  margin-top: 0.2em;
}

2. エラーの通知 (ARIAライブリージョン)

エラーメッセージが動的に表示された際に、スクリーンリーダーユーザーにその発生を即座に伝えるためには、ARIAライブリージョンを使用します。

エラーメッセージを含むコンテナ要素にaria-live属性を設定します。

<div id="live-region-for-errors" aria-live="assertive" aria-atomic="true">
  <!-- ここにエラーメッセージが動的に追加される -->
</div>

<form>
  <div>
    <label for="name">名前</label>
    <input type="text" id="name">
    <div id="name-error" class="error-message" style="display: none;">名前は必須です。</div>
  </div>
  <div>
    <label for="age">年齢</label>
    <input type="number" id="age">
    <div id="age-error" class="error-message" style="display: none;">年齢は数字で入力してください。</div>
  </div>
  <button type="submit">送信</button>
</form>

JavaScriptでエラーが発生した場合、該当するエラーメッセージ要素を表示し、同時にそのエラーメッセージのテキストをライブリージョンコンテナに追加(または既存のテキストを更新)します。

const liveRegion = document.getElementById('live-region-for-errors');
const nameInput = document.getElementById('name');
const nameError = document.getElementById('name-error');

function validateForm() {
  let hasError = false;
  let errorMessages = [];

  // 名前フィールドのバリデーション
  if (nameInput.value.trim() === '') {
    nameError.style.display = 'block';
    nameInput.setAttribute('aria-invalid', 'true');
    nameInput.setAttribute('aria-describedby', 'name-error');
    errorMessages.push('名前は必須です。'); // ライブリージョン用のメッセージに追加
    hasError = true;
  } else {
    nameError.style.display = 'none';
    nameInput.removeAttribute('aria-invalid');
    nameInput.removeAttribute('aria-describedby');
  }

  // 他のフィールドのバリデーション...

  // ライブリージョンへの通知
  if (errorMessages.length > 0) {
    // 単純な例:最初のエラーを通知
    // 実際にはエラーサマリーなどをここに設定することが多い
    liveRegion.textContent = errorMessages[0];
  } else {
    liveRegion.textContent = ''; // エラーがなくなったらクリア
  }

  return !hasError; // エラーがなければtrueを返す
}

// フォーム送信時などにvalidateFormを実行
const form = document.querySelector('form');
form.addEventListener('submit', function(event) {
  if (!validateForm()) {
    event.preventDefault(); // 送信をキャンセル
    // エラーがある場合、最初のエラー要素にフォーカスを移すなど検討
    if (document.querySelector('[aria-invalid="true"]')) {
      document.querySelector('[aria-invalid="true"]').focus();
    }
  }
});

注意点: * aria-live属性は、要素が最初から存在しており、その内容がJavaScriptによって動的に変更される場合に効果を発揮します。要素自体を動的に追加する場合は、追加後にaria-live属性を設定するか、親要素に設定しておく必要があります。 * ライブリージョンは乱用するとユーザー体験を損ねる可能性があります。本当に重要な、即時通知が必要な情報(エラー、成功メッセージなど)のみに使用してください。

3. エラーの集約 (エラーサマリー)

特にフォームが長い場合や、複数のエラーがある場合に有効なのが、エラーサマリーです。ページの上部など目立つ位置にエラーの一覧を表示し、それぞれのリンクをクリックすると該当する入力フィールドにジャンプできるようにします。

<div id="error-summary" role="alert" style="display: none;">
  <h2>入力内容にエラーがあります</h2>
  <ul id="error-list">
    <!-- エラーが発生した際にJavaScriptでリスト項目を追加 -->
  </ul>
</div>

<form>
  <div>
    <label for="name">名前</label>
    <input type="text" id="name">
    <div id="name-error" class="error-message" style="display: none;">名前は必須です。</div>
  </div>
  <div>
    <label for="email">メールアドレス</label>
    <input type="email" id="email">
    <div id="email-error" class="error-message" style="display: none;">有効なメールアドレスを入力してください。</div>
  </div>
  <button type="submit">送信</button>
</form>

JavaScriptでの実装例:

const errorSummary = document.getElementById('error-summary');
const errorList = document.getElementById('error-list');
const nameInput = document.getElementById('name');
const nameError = document.getElementById('name-error');
const emailInput = document.getElementById('email');
const emailError = document.getElementById('email-error');

function validateFormWithSummary() {
  let hasError = false;
  let errors = []; // [{ fieldId: 'name', message: '名前は必須です。' }, ...]

  // 名前フィールドのバリデーション
  if (nameInput.value.trim() === '') {
    nameError.style.display = 'block';
    nameInput.setAttribute('aria-invalid', 'true');
    nameInput.setAttribute('aria-describedby', 'name-error');
    errors.push({ fieldId: 'name', message: '名前は必須です。' });
    hasError = true;
  } else {
    nameError.style.display = 'none';
    nameInput.removeAttribute('aria-invalid');
    nameInput.removeAttribute('aria-describedby');
  }

  // メールアドレスフィールドのバリデーション
  if (emailInput.value !== '' && !isValidEmail(emailInput.value)) { // isValidEmailは別途実装
     emailError.style.display = 'block';
     emailInput.setAttribute('aria-invalid', 'true');
     emailInput.setAttribute('aria-describedby', 'email-error');
     errors.push({ fieldId: 'email', message: '有効なメールアドレスを入力してください。' });
     hasError = true;
  } else {
    emailError.style.display = 'none';
    emailInput.removeAttribute('aria-invalid');
    emailInput.removeAttribute('aria-describedby');
  }


  // エラーサマリーの更新
  errorList.innerHTML = ''; // 一度クリア
  if (errors.length > 0) {
    errors.forEach(error => {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = `#${error.fieldId}`; // エラー箇所へのアンカーリンク
      a.textContent = error.message;
      a.addEventListener('click', (event) => {
        event.preventDefault();
        document.getElementById(error.fieldId).focus(); // エラー箇所にフォーカス
      });
      li.appendChild(a);
      errorList.appendChild(li);
    });
    errorSummary.style.display = 'block';

    // エラーサマリーのタイトルまたは最初のリンクにフォーカスを移す
    // これにより、スクリーンリーダーユーザーはエラーの一覧から確認を開始できる
    errorSummary.querySelector('h2') ? errorSummary.querySelector('h2').focus() : errorList.querySelector('a').focus();

  } else {
    errorSummary.style.display = 'none';
  }

  return !hasError;
}

// 簡単なメールバリデーション関数(例)
function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// フォーム送信時などにvalidateFormWithSummaryを実行
const formWithSummary = document.querySelector('form');
formWithSummary.addEventListener('submit', function(event) {
  if (!validateFormWithSummary()) {
    event.preventDefault();
  }
});

// ページロード時にエラーサマリーの tabindex を -1 に設定しておくと、
// JavaScriptでフォーカスを当てるまでタブ順に含まれない。
// HTML: <div id="error-summary" role="alert" style="display: none;" tabindex="-1">
// フォーカス設定時: errorSummary.focus();

エラーサマリーにrole="alert"を設定することで、エラーが発生したことを通知できます。また、エラーサマリー内のリンクをクリックした際に、該当フィールドにフォーカスを移すことが重要です。これにより、キーボードユーザーやスクリーンリーダーユーザーが簡単にエラー箇所へ移動し、修正できます。

実装時の注意点とよくある落とし穴

テスト方法

実装したアクセシビリティ対応が正しく機能しているかを確認するためには、以下の方法でテストを行います。

  1. キーボード操作: マウスを使わず、Tabキー、Shift+Tabキー、Enterキー、Spaceキーなど、キーボードのみでフォームを操作し、エラーが発生した場合に
    • エラーメッセージが関連フィールドの近くに表示されるか
    • エラーサマリーが表示された場合に、サマリー内のリンクで該当フィールドにジャンプできるか
    • エラーのあるフィールドにフォーカスが当たったときに、エラーメッセージが(aria-describedbyなどで)関連付けられていることがわかるか(これはスクリーンリーダーでの確認も必要)
    • フォーム送信後にエラーサマリーや最初のエラーフィールドにフォーカスが移動するか などを確認します。
  2. スクリーンリーダーの使用: NVDA (Windows, 無償)、JAWS (Windows, 有償)、VoiceOver (macOS/iOS, 標準搭載) などのスクリーンリーダーを使用してテストを行います。
    • フォームフィールドにフォーカスを当てた際に、ラベルに加えてエラー状態やエラーメッセージが読み上げられるか(aria-invalid, aria-describedby
    • エラーメッセージが動的に表示された際に、ライブリージョンによって通知されるか
    • エラーサマリーが表示された場合に、サマリーの内容(エラー件数、各エラーメッセージ)が読み上げられるか
    • エラーサマリーのリンクから該当フィールドにジャンプした際に、正しいフィールドにフォーカスが移動するか などを確認します。
  3. アクセシビリティ評価ツール: Lighthouse (Chrome DevToolsに内蔵)、axe DevTools、Wave Extensionなどの自動評価ツールを使用して、aria-invalidなどの属性が正しく設定されているか、基本的なアクセシビリティの問題がないかを確認します。ただし、これらのツールだけでは動的な通知やフォーカス管理の挙動は完全にテストできないため、手動でのテストと組み合わせることが重要です。

まとめ

アクセシブルなエラーメッセージの実装は、すべてのユーザーがウェブサイトを円滑に利用するために不可欠です。単に視覚的にエラーを表示するだけでなく、aria-describedbyによる関連付け、ARIAライブリージョンによる動的な通知、そして場合によってはエラーサマリーの提供を組み合わせることで、より多くのユーザーにエラー情報を確実に伝えることができます。

今回解説したコード例や手順を参考に、ご自身のプロジェクトでエラーメッセージのアクセシビリティ対応を実践してみてください。そして、実装後は必ずキーボードやスクリーンリーダーを使ったテストを行い、実際にユーザーがどのように情報を受け取るかを確認することが非常に重要です。一歩ずつ着実に、アクセシブルなウェブサイト構築を目指しましょう。