フォームはウェブサイトの中でもインタラクションの多い箇所です。ユーザー側にきちんと情報を伝え、そして正しく入力してもらう必要があるのでアクセシビリティーには気をつけたいです。アクセシビリティー対応といえばWAI-ARIAによる支援がありますが、この記事ではWAI-ARIAに限らずどう対応するべきなのか、デモを用いて紹介します。
バリデーションに関してシンプルに実現できるものと、ちょっと凝ったリアルタイムバリデーションのものと2例用意しています。後者は動的に変化するコンテンツへのアクセシビリティー対応について解説しています。
▼シンプルなバリデーション
▼ちょっと凝ったバリデーション
まずはセマンティックなマークアップを
WAI-ARIAを使ったコーディングというと、とにかくrole
属性やaria-label
属性でマークアップすればよいかと思うかもしれませんが、必ずしもそうとは限りません。WAI-ARIAはあくまでセマンティックの支援なので、HTMLとして用意されているタグがあれば、それを用いるべきです。具体的にはボタンには<a>
タグや<button>
タグを原則的には使用しましょう。
静的なマークアップの限界
ただ、できる限りネイティブのタグを用いても現代のウェブですべてのアクセシビリティーを賄うのは難しいです。とくに状態の変化について知らせるのは、静的なマークアップだけで対応するのが困難なポイントです。そのような状態の変化についてはWAI-ARIAとJavaScriptを使って支援技術(スクリーンリーダーなどの、障害のあるユーザーへのインタラクションを容易にする技術)へ通知します。
フォームのアクセシビリティー
それでは具体的なフォームのアクセシビリティー対応をコードも含めてみていきます。
ラベル付け
入力要素に適切にラベル付けすることで視覚的にもアクセシビリティー的にも関連付けできます。
まずはフォーム自体のラベルです。デモの見た目では「お問い合わせフォーム」という見出しがついているので、このフォームがお問い合わせフォームであることは見れば分かります。その一方でHTML上では「見出し」と「フォーム」という要素が並んでいるだけであり、HTMLのセクショニング・コンテンツ以上の関連付けはされていません。
そこでaria-labelledby
属性を使ってラベル付けをします。labeled byとあるようにラベル付けする方にラベルを示す要素をid
属性で指定します。
<h1 id="formTitle" class="formTitle">お問い合わせフォーム</h1>
<form aria-labelledby="formTitle"></form>
これで<form>
はid=formTitle
という要素(<h1>
タグ)でラベルしています、という意味になります。
続いてフォームの各入力要素のラベリングです。<label>
タグとfor
属性を用いればネイティブなHTMLだけでラベリングします。for
属性にラベルしたい要素のid
属性をいれます。
<label for="name" class="labelName">お名前 (必須)</label>
<input
type="text"
id="name"
class="input"
required
aria-required="true"
/>
これで<input>
とお名前 *
を関連付けできます。
あるいは<label>
タグで囲ってしまうのも有効です。この場合はラベル対象が明確なのでfor
属性はいりません。
<label class="labelName">お名前 (必須)
<input
type="text"
id="name"
class="input"
required
aria-required="true"
/>
</label>
事情により<label>
タグを使えない場合は<form>
と同じようにaria-labelledby
属性を使用します。<label>
タグとは付ける方と付けられる方の関係が逆になるので注意です。
<span id="nameLabel" class="labelName">お名前 (必須)</span>
<input
type="text"
id="name"
class="input"
required
aria-required="true"
aria-labelledby="nameLabel"
/>
フォームの説明
ラベルとは別にフォームの説明と関連付けたい場合はaria-describedby
属性を使用します。今回のデモではパスワード入力欄に使っています。
<input
type="Password"
id="password"
class="input"
required
aria-required="true"
pattern="^[0-9A-Za-z]+$"
minlength="6"
maxlength="18"
aria-describedby="passwordType"
/>
<span id="passwordType" class="describe">(半角英数6文字以上18文字以下)</span>
aria-describedby="passwordType"
でid="passwordType"
の要素とひもづけることで、パスワード入力欄は「半角英数6文字以上18文字以下」であることを説明しています。
必須要素
今回のフォームではすべての入力項目が必須になっています。required
属性を使えば入力項目が必須であることをブラウザに伝えられます。NVDAなどのスクリーンリーダーはこれだけでも必須である旨を読み上げてくれます。一方でWAI-ARIAにもaria-required
属性というものがあります。
これらに関してW3CのWeb Content Accessibility Guidelinesへのテクニック集には、required
属性に加えてaria-required="true"
属性を付与することで一部の支援技術への手助けになる(※1)、とあるので同時に付与しておくとよいでしょう。
<input
type="text"
id="name"
class="input"
required
aria-required="true"
/>
バリデーションのアクセシビリティー
ここまでは静的な部分についてアクセシビリティー対応を説明しました。次に、動的な要素である入力バリデーションについて説明します。
手軽なのはHTML5からネイティブで用意されているバリデーションを使うことです。
これは<input>
タグのrequired
属性やtype
属性、pattern
属性をもとに入力された内容を送信ボタン押下時に判定します。エラーがある場合はその項目にエラーメッセージが表示されます。特別なアクセシビリティー対応をせずとも、それらのエラーメッセージをスクリーンリーダーは読み上げるのでエラーへのアクセシビリティー対応はシンプルに実現できます。
とはいえ、デザインの都合や送信ボタンを押さずともバリデーションをしたい場合などは自前で実装する必要があります。
こうした動的に内容が変わる部分についてはWAI-ARIAで補強する必要があります。
エラーステータス
メッセージの表示・非表示は以下のスクリプトで行っています。
/**
* aria-hidden属性を変更します
* @param event
*/
const changeAriaHidden = event => {
const inputElement = event.target;
const liveRegionElement = inputElement.parentNode.querySelector(
"span[role='status']"
);
if (liveRegionElement == null) {
return;
}
if (inputElement.validity.valid) {
// inputタグのバリデーションがvalidならばOKメッセージを表示
liveRegionElement.querySelector(".OKMessage").setAttribute("aria-hidden", "false");
} else {
// inputタグのバリデーションがinvalidならばエラーメッセージを表示
liveRegionElement
.querySelector(".errorMessage")
.setAttribute("aria-hidden", "false");
}
};
スクリプト自体はaria-hidden
属性の切り替えを行っています。aria-hidden
属性はコンテンツが表示されているか否かを知らせる属性です。aria-hidden="true"
であれば、そのコンテンツは表示されていない状態です。
変更イベントを受け取ると、変更された要素inputElement
とメッセージのラッパー要素liveRegionElement
を取得します。入力要素のバリデーションはinputElement.validity.valid
で判定できるので、妥当な場合.OKMessage
要素のaria-hidden
属性をfalse
、つまり表示に変更します。そうでない場合は.errorMessage
をaria-hidden="false"
にしてエラーの方を表示させています。
なお、setAriaHidden
関数で入力中には両方のメッセージが非表示となるようにしています。
メッセージ部分のHTMLを見てみます。ラッパー要素にはaria-live="polite"
とrole="status"
という属性がついてます。
<span aria-live="polite" role="status">
<span class="errorMessage messageBox" aria-hidden="true"
>正しい形式で入力してください</span
>
<span class="OKMessage messageBox" aria-hidden="true">OKです!</span>
</span>
aria-live
はライブリージョンとも呼ばれ動的なコンテンツであることを支援技術へ知らせます。polite
という値は更新されたことをどの優先度で通知するかを設定します。polite
は区切りの良いタイミングで通知します。読み上げで例を挙げれば、進行中の読み上げが終わった後に、変更が読み上げられるような感じです。それに対してassertive
はpolite
よりも優先度が高く、すぐに通知します。
もう1つの属性role="status"
はここがステータスを表す部分であることを示しています。似たようなロールとしてrole="alert"
もありますが、今回はOKとエラー両方を表示するのでrole="status"
としました。エラーのみ表示の場合はrole="alert"
が良いでしょう。
ライブリージョンがあることで子要素の変更を検知します。このデモで動的に変わるのは.messageBox
要素です。この要素の表示・非表示の変更を検知し、変化があったことを支援技術へ伝えられます。
つづいてCSSを見てみます。
.messageBox[aria-hidden=true] {
display: none;
}
CSSの属性セレクターとしてWAI-ARIAを利用しています。WAI-ARIA自体はHTMLの属性なのでこのような使い方ができます。今回は表示・非表示の状態をaria-hidden
属性を使ってJavaScriptで制御しています。そのため新たにCSS切り替え用のクラス名などを用意しなくても属性セレクターで切り替えできます。
上記のとおり、メッセージはaria-hidden="true"
がついている時は非表示にaria-hidden="false"
の時は効かないので表示される仕組みです。
ボタンの活性・非活性
ボタンの活性・非活性というとdisalbed
属性がありますが、これには問題点があります(※2)。disabled
になっているとタブキーによってフォーカスせず、ボタンに到達できません。このままではスクリーンリーダーで読み上げてもらえず、ないものになってしまいます。
そこで使うのがaria-disabled
属性です。ネイティブのdisabled
属性と違い、ボタンのフォーカスは効くうえ、スクリーンリーダーでは「無効なボタン」である旨を読み上げてくれます。
ボタンの活性・非活性はフォーム全体のバリデーションに紐付いていますが、これは<form>
タグへの疑似クラス:valid
を使って、その要素の有無で判定できます。
const validForm = document.querySelector("form:valid");
validForm
がnull
ならバリデーションエラーのある状態、そうでない場合はOKな状態で判定できます。これを使ってボタンの活性・非活性とメッセージの表示の切り替えを行っています。
const submitButton = document.querySelector("#submit");
const formValidateMessage = document.querySelector("#formValidate");
// フォーム全体が妥当なら送信ボタンのaria-disabledをfalse、そうでない場合はtrue
submitButton.setAttribute("aria-disabled", String(validForm === null));
// ボタンのステータスメッセージの表示・非表示
if (validForm !== null) {
formValidateMessage
.querySelector(".OKMessage")
.setAttribute("aria-hidden", "false");
formValidateMessage
.querySelector(".errorMessage")
.setAttribute("aria-hidden", "true");
} else {
formValidateMessage
.querySelector(".OKMessage")
.setAttribute("aria-hidden", "true");
formValidateMessage
.querySelector(".errorMessage")
.setAttribute("aria-hidden", "false");
}
余談にはなりますが、スクリーンリーダーを利用しない人にはボタンの活性・非活性だけでもフォームのバリデーションを確認できるので、フォーム全体のステータスメッセージに関してはopacity: 0
で見えなくするのも可能です。visibility: hidden
やdisplay: none
だと読み上げされなくなってしまうので、opacity
を使って隠す必要があります。高さを消す場合には別途height: 0
やposition: absolute
などを使って打ち消してください。
逆にデザイン上どうしても入れたくないが、アクセシビリティーを確保するための手法としても使えるでしょう。とはいえ、視覚的要素の配慮だけがアクセシビリティーではないので、すべての人が等しく使えるようなデザインにすることも大切です。
アクセシビリティー対応にはいろいろなやり方がある
ここで挙げたのはアクセシビリティー対応への一例です。今回はWAI-ARIAを使ったアクセシビリティーについて主に解説していきましたが、文字の大きさ・コントラストといった観点もアクセシビリティーの中にはあります。
必ずしもこれが正解というわけではありませんが、誰でも扱えるようにするのがアクセシビリティー対応の第一義です。やり方がいろいろあって迷うかもしれませんが、何もやらないよりはやった方がアクセシビリティーに寄与できます。まずは、わかるところから始めるのも良いでしょう。
ICS MEDIAでは、他にもアクセシビリティーに関する記事が公開されています。あわせてご覧ください。
- ウェブ制作に関わる人に役立つウェブアクセシビリティーの基本
- アクセシビリティーに考慮するHTMLコーディング - WAI-ARIAでスクリーンリーダーの読み上げがこう変わる
- 今どきの入力フォームはこう書く! HTMLコーダーがおさえるべきinputタグの書き方まとめ
補足
本記事の読み上げソフトには「NVDA(日本語版)」とmacOSの「VoiceOver」を使用して検証しました。
参照記事
※この記事が公開されたのは4年前ですが、半年前の4月に内容をメンテナンスしています。