1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

aria-liveが読み上げられない!?

Posted at

はじめに

aria-liveを使って、フォームのエラー内容を実装したところ、スクリーンリーダーに読み上げられない実装をしてしまったので、そのやらかしを記事にしました。

aria-liveとは

コンテンツが最初に読み込まれた後に変更されると、支援技術 (AT) ユーザーはその変更を「見る」ことができない場合があります。変更の中には重要なものもあります。また、重要でないものもあります。 aria-live 属性は、ユーザーに更新情報を通知し、重要性と緊急性に基づいて、AT ユーザーにコンテンツの変更を即座に通知するか、積極的に通知するか、受動的な通知をするかを開発者が選ぶことができます。

mdnより引用

今回ではフォームのテキスト入力でバリデーションエラーが出た場合、スクリーンリーダーに通知して読み上げられるまでが実装のゴールでした。

実際にどんなコード書いたのか

// 失敗したコード
{isError &&(
  <p aria-live="polite">エラーです</p>
)}

アクセシビリティの実装を普段している人から見たらひと目で分かると思います。
エラーが出たらaria-liveが付与されたpタグが表示されるというコードです。
エラーが出るまでは、aria-liveが付与されたpタグは存在していません。

フリー株式会社のアクセシビリティガイドライン「注意:ページのロード時にARIAライブ・リージョンが存在しないと読み上げられない」 と書いてあるように最初から存在していないと読み上げられないのです。

以下が正しく動作するコードです。

// 正しいコード
 <p aria-live="polite">
  {isError && "エラーです"}
 </p>

Demo

実際にスクリーンリーダーに反応するかどうかデモを実装しました。
StackBlitzにアップロードしてあります。

App.tsx

import { useState } from 'react';
import type { ChangeEvent } from 'react';
import { z } from 'zod';
import './App.css';

const inputSchema = z
  .string()
  .min(5, '5文字以上入力してください')
  .max(10, '10文字以内で入力してください');

function App() {
  // 良い例用のstate
  const [goodInputValue, setGoodInputValue] = useState<string>('');
  const [goodError, setGoodError] = useState<string>('');

  // 悪い例用のstate
  const [badInputValue, setBadInputValue] = useState<string>('');
  const [badError, setBadError] = useState<string>('');

  // 良い例のハンドラー
  const handleGoodChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setGoodInputValue(value);

    const result = inputSchema.safeParse(value);

    if (!result.success) {
      setGoodError(result.error.issues[0].message);
    } else {
      setGoodError('');
    }
  };

  // 悪い例のハンドラー
  const handleBadChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setBadInputValue(value);

    const result = inputSchema.safeParse(value);

    if (!result.success) {
      setBadError(result.error.issues[0].message);
    } else {
      setBadError('');
    }
  };

  return (
    <>
      <h1>aria-live="polite" の使い方</h1>
      {/* 良い例 */}
      <section className="example good">
        <h2>✓ 良い例</h2>
        <p className="description">
          aria-live領域を常にDOMに配置し、中身だけを切り替える
        </p>

        <input
          type="text"
          value={goodInputValue}
          onChange={handleGoodChange}
          placeholder="5文字以上10文字以内で入力"
          className="input-field"
        />

        <div aria-live="polite" className="live-region">
          {goodError && <span className="error-message">{goodError}</span>}
        </div>
      </section>
      <section className="example bad">
        <h2>✗ 悪い例</h2>
        <p className="description">
          aria-live要素自体を条件付きレンダリング(スクリーンリーダーが変更を検知できない)
        </p>

        <input
          type="text"
          value={badInputValue}
          onChange={handleBadChange}
          placeholder="5文字以上10文字以内で入力"
          className="input-field"
        />

        {badError && (
          <div aria-live="polite">
            <span className="error-message">{badError}</span>
          </div>
        )}
      </section>
    </>
  );
}

export default App;


補足

SSR(サーバーサイドレンダリング)だとaria-liveは機能しないので、idaria-describedbyでエラー文章を紐づけてaria-invalid="true"を返す方が良いようです。

<input
 type="text"
 aria-describedby="errorId"
 aria-invalid={isError ? true : false}
 />
 
{isError && (
    <p id="errorId">
      エラーです
    </p>
)}

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?