はじめてみよう!
お問い合わせフォームのウェブアクセシビリティ対応の方法

41
52
117

フォームはウェブサイトの中でもインタラクションの多い箇所です。ユーザー側にきちんと情報を伝え、そして正しく入力してもらう必要があるのでアクセシビリティには気をつけたいです。アクセシビリティ対応といえばWAI-ARIAによる支援がありますが、この記事ではWAI-ARIAに限らずどう対応するべきなのか、デモを用いて紹介します。

バリデーションに関してシンプルに実現できるものと、ちょっと凝ったリアルタイムバリデーションのものと2例用意しています。後者は動的に変化するコンテンツへのアクセシビリティ対応について解説しています。

▼シンプルなバリデーション

▼ちょっと凝ったバリデーション

まずはセマンティックなマークアップを

WAI-ARIAを使ったコーディングというと、とにかくrole属性やaria-label属性でマークアップすればよいかと思うかもしれませんが、必ずしもそうとは限りません。WAI-ARIAはあくまでセマンティックの支援なので、HTMLとして用意されているタグがあれば、それを用いるべきです。具体的にはボタンには<a>タグや<button>タグを原則的には使用しましょう。

静的なマークアップの限界

ただ、できる限りネイティブのタグを用いても現代のウェブですべてのアクセシビリティを賄うのは難しいです。とくに状態の変化について知らせるのは、静的なマークアップだけで対応するのが困難なポイントです。そのような状態の変化についてはWAI-ARIAとJavaScriptを使って支援技術(スクリーンリーダーなどの、障害のあるユーザーへのインタラクションを容易にする技術)へ通知します。

フォームのアクセシビリティ

それでは具体的なフォームのアクセシビリティ対応をコードも含めてみていきます。

ラベル付け

入力要素に適切にラベル付けすることで視覚的にもアクセシビリティ的にも関連付けできます。

まずはフォーム自体のラベルです。デモの見た目では「お問い合わせフォーム」という見出しがついているので、このフォームがお問い合わせフォームであることは見れば分かります。その一方でHTML上では「見出し」と「フォーム」という要素が並んでいるだけであり、HTMLのセクショニング・コンテンツ以上の関連付けはされていません。

そこでaria-lebelledby属性を使ってラベル付けをします。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-lebelledby属性を使用します。<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、つまり表示に変更します。そうでない場合は.errorMessagearia-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は区切りの良いタイミングで通知します。読み上げで例を挙げれば、進行中の読み上げが終わった後に、変更が読み上げられるような感じです。それに対してassertivepoliteよりも優先度が高く、すぐに通知します。

もう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");

validFormnullならバリデーションエラーのある状態、そうでない場合は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: hiddendisplay: noneだと読み上げされなくなってしまうので、opacityを使って隠す必要があります。高さを消す場合には別途height: 0position: absoluteなどを使って打ち消してください。

逆にデザイン上どうしても入れたくないが、アクセシビリティを確保するための手法としても使えるでしょう。とはいえ、視覚的要素の配慮だけがアクセシビリティではないので、すべての人が等しく使えるようなデザインにすることも大切です。

アクセシビリティ対応にはいろいろなやり方がある

ここで挙げたのはアクセシビリティ対応への一例です。今回はWAI-ARIAを使ったアクセシビリティについて主に解説していきましたが、文字の大きさ・コントラストといった観点もアクセシビリティの中にはあります。

必ずしもこれが正解というわけではありませんが、誰でも扱えるようにするのがアクセシビリティ対応の第一義です。やり方がいろいろあって迷うかもしれませんが、何もやらないよりはやった方がアクセシビリティに寄与できます。まずは、わかるところから始めるのも良いでしょう。

補足

本記事の読み上げソフトには「NVDA(日本語版)」とmacOSの「VoiceOver」を使用して検証しました。

参照記事