フォーム入力不備の情報提示 (スクリーンリーダーのユーザーにも伝わるように)

ウェブサイトのフォームで、入力に不備がある場合、問題となっている箇所および修正方法をユーザーに提示する必要があります。そのような情報は当然ながら、支援技術 (スクリーンリーダーなど) のユーザーにも伝わらなければなりません。

WCAG 2.0 / JIS X8341-3:2010 では、以下の規定があります。

入力エラーが自動的に発見された場合は、エラーとなっている箇所を特定し、そのエラーをテキストで説明しなければならない。

出典 : JIS X8341-3:2010「7.3.1.1 入力エラー箇所の特定に関する達成基準」(等級A)

入力エラーが自動的に発見された場合は、その修正方法が明らかであれば、その方法を利用者に提示しなければならない。ただし、セキュリティ又はコンテンツの目的を損なう場合は除く。

出典 : JIS X8341-3:2010「7.3.1.3 入力エラー修正方法の提示に関する達成基準」(等級AA)

今回の記事では、これを実現するための方法を考えてみたいと思います。

HTML5 の required 属性を記述する

実装が容易な方法としては、入力必須の要素 (<input> 要素や <textarea> 要素) に HTML5 の required 属性を記述することが考えられます。今どきのブラウザは、required 属性が記述された要素に入力不備がある場合、ブラウザ側がポップアップメッセージを出してくれるからです。

required 属性が記述された要素に入力不備がある場合、ブラウザ側がポップアップメッセージを出してくれる (Chrome の例)。
required 属性が記述された要素に入力不備がある場合のポップアップメッセージ (Chrome の例)

私の手元の環境では、このようなメッセージは Chrome、Firefox、Opera、そして Internet Explorer (IE11) で表示されるのを確認しています (Chrome、Firefox、Opera は、Windows 版や OS X 版だけでなく、Android 版でも表示されます)。

なお HTML5 では、required 属性以外でも同様のポップアップメッセージを出すことができます。たとえば入力要素 (<input>) が type="email" の場合、入力フィールド内に何らかの文字が入力されていると簡易的なバリデートが行なわれ、@ (アットマーク) が無い場合や@の前後に文字がない場合にメッセージが表示されます。また、pattern 属性によって各入力要素の細かなバリデート/メッセージ出力も可能になっています (ご参考 : html5pattern.com)。

スクリーンリーダーでの読み上げは不十分

ただこのポップアップメッセージは、スクリーンリーダーでの読み上げとなると、現時点ではまだ不十分のようです。理想的には、フォームのサブミットボタンが押されると同時に、入力不備のフィールド (複数ある場合はその先頭のもの) にフォーカスが移動し、ポップアップメッセージとラベル (<label> 要素) の両方が同時に読み上げられることだと思いますが、現実はそうではありません。

私の手元の環境で検証した限りでは、NVDA+Firefox の組み合わせが理想に近い挙動をしたのですが、入力不備のフィールドにフォーカスが移動してポップアップメッセージは読み上げられるものの、ラベル (<label> 要素) が併読されないため、「何を入力する項目なのか」が伝わりにくくなっています。

他の組み合わせ (たとえば NVDA+Chrome、NVDA+IE 、OS X VoiceOver+Chrome、OS X VoiceOver+Firefox、Android TalkBack+Chrome、Android TalkBack Firefox) では、入力不備のフィールドにフォーカスが移動するものの、ポップアップメッセージ、ラベル共に、スクリーンリーダーで読み上げられませんでした。

ポップアップメッセージを表示しないブラウザもある

さらに厄介なことに、iOS および OS X の Safari、iOS の Chrome では、入力不備の際にこのようなポップアップメッセージを出してくれません。特に iOS (iPhone や iPad など) は利用者が多いモバイルプラットフォームなので、入力不備の情報提示を HTML5 の required 属性に依存しすぎるのは、現時点では問題かもしれません。

動的にバリデート/メッセージ出力する仕組みを用意し WAI-ARIA を記述する

もうひとつの実現方法として、入力必須の要素 (<input> 要素や <textarea> 要素) には required 属性を記述せず (入力必須である旨のマークアップとしては当面 aria-required="true" 属性のみを記述する)、入力不備については別途動的にバリデート/メッセージ出力する仕組みを用意し、出力されるメッセージを WAI-ARIA によってスクリーンリーダーのユーザーにも伝わりやすくする、というのも考えられます。

出力されるメッセージは、二種類あるとよいでしょう。ひとつは、入力不備がある旨を強制的に伝えるための「全体的なアラート」、もうひとつは、個々の入力不備の箇所に対して具体的な不備の内容や修正方法を提示する「個別メッセージ」です。

入力不備がある旨を強制的に伝えるための「全体的なアラート」と、個々の入力不備の箇所に対して具体的な不備の内容や修正方法を提示する「個別メッセージ」。
「全体的なアラート」と「個別メッセージ」

全体的なアラート

「全体的なアラート」は、フォームのサブミットボタンが押されてバリデート結果が出ると同時にスクリーンリーダーで優先的に (強制的に) 読み上げられるように、WAI-ARIA の role="alert" および aria-live="assertive" を記述します。

role="alert" が記述された要素は、暗黙的に aria-live="assertive" かつ aria-atomic="true" の値を持つようなので (ご参考 : 「Accessible Rich Internet Applications (WAI-ARIA) 1.0 日本語訳」の「5. ロールモデル」の「alert (ロール)」の項 )、本来は role="alert" のみを記述すればよいと思いますが、一部の組み合わせ (Android TalkBack+Chrome や NVDA+IE など) では role="alert" だけだと優先的な読み上げがなされないので、aria-live="assertive" も併記するとよいでしょう (ご参考 : Manatee Road の role="alert" 実験ページ)。

なお、role="alert" や aria-live="assertive" は、JavaScript によって (ページの再ロードを伴わずに) 表示内容を変化させるケースでの使用が前提なのでしょうか、PHP でフォーム入力内容をバリデートしてその結果をページの再ロードで表示させるような場合は、role="alert" や aria-live="assertive" を優先的に読み上げないユーザーエージェントの組み合わせ (スクリーンリーダー+ブラウザ) もあるようです。そういったケースへの対策としては、「全体的なアラート」のブロックに tabindex を設定しておき (値は「-1」でよいでしょう)、<body> 要素に onload 属性を記述してそのブロックにまずフォーカスが当たるようにするとよいかもしれません。

個別メッセージ

個々の入力不備の箇所に対して提示される「個別メッセージ」は、aria-describedby を用いて、入力フィールドにフォーカスが当たるとメッセージも同時に読み上げられるようにするとよいでしょう。

ただし、OS X VoiceOver では入力フィールドにフォーカスが当たっても aria-describedby で紐付けられたテキスト (メッセージ) が同時に読み上げられないようです (実は5~6秒待てば読み上げられますが、多くのユーザーはそこまで待てないでしょう…)。このため、入力不備を示す「個別メッセージ」は (aria-describedby で参照される ID 値を持ちつつも) <label> 要素内に出力させるとよいかもしれません。

また、NVDA+IE では aria-describedby で複数のテキスト要素が同時に紐付けられている場合、いずれのテキストも読み上げられないようです。入力不備を示す「個別メッセージ」とは別に、あらかじめフォーム入力要素に注釈 (記入フォーマットやヒントなど) が用意されている (aria-describedby で入力フィールドと紐付けられている) 場合は、注意が必要です。


以上、手元の環境での試行錯誤を交えつつ、まとめてみました (当サイトのお問い合わせフォームでも実験がてら実装しています)。ご参考になれば幸いですが、理想的にはやはり HTML5 の required 属性 (や pattern 属性、type="email") によるメッセージがあらゆるブラウザで表示されて、かつ併用するスクリーンリーダーで (<label> 要素と併せて) それらがきちんと読み上げられるようになればいいのにな…とは思います。