弊社はウェブアクセシビリティ対応関連サービスを主要なお仕事にさせていただいている関係で、公共、および企業ウェブサイトに対するウェブアクセシビリティ試験(「ウェブアクセシビリティチェック」という呼び方もしますね)をお手伝いすることも多いです。

今年もおかげさまで多くのご依頼をいただき、様々なウェブサイトのウェブアクセシビリティを試験させていただきましたが、そこで「問題あり」、つまり試験対象となった達成基準を満たしていないと判断し、修正が必要ですと指摘させていただいた箇所(以降、簡単に書くために「エラー」「エラー箇所」と記します)にはある程度の傾向があります。

つまり、業種や業界、あるいは公共サイトか企業サイトかなどに関係なく、よく見つかるウェブアクセシビリティ上のエラーはある程度共通しているということです。

そこで今回は、私が今年1年間で担当したウェブアクセシビリティ試験全体において、「このエラーは頻繁に指摘したな......」という達成基準と、こういう実装はやりがちで、しかもウェブアクセシビリティ上の問題がありますよ、という実装例について、「トップ 5」を紹介してみようと思います。

「トップ 5」とはいっても、そこまで厳密に指摘した回数を全部数えるようなことまではやっていませんので、あくまで私の印象で多かった順だと思ってください。

要するにここで挙げたものは、ウェブアクセシビリティ試験で指摘されがちなエラーだと思って読んでいただけるとよいと思います。逆にいうと、ここで書いたようなことだけでもまず見なおしていただければ、ウェブアクセシビリティ上の問題をかなり減らす事ができると思いますので、ウェブサイトを実装する方や運営する方にとって、何かしらの参考になれば幸いです。

なお、弊社にウェブアクセシビリティ試験をご依頼いただく場合で最も多い要件が「JIS X 8341-3:2016(WCAG 2.0)」「適合レベル AA」ですので、今回挙げる達成基準も、それらに準じたものになります。

本記事内で紹介しているサンプルソースコード(特に改善案)はあくまでひとつの実装例であり、このような実装がウェブアクセシビリティガイドラインにおける達成基準を満たすための「模範解答」や「絶対に正しい実装」というわけではありません。また、問題のある実装として例に挙げたソースコードも、特定のクライアント様のウェブサイトにおける実装例を具体的に示したものではありません。


第5位 自動で動き続けるカルーセルなど(達成基準 2.2.2 一時停止、停止及び非表示)

あるウェブサイトに対してウェブアクセシビリティ試験を行う場合、トップページはまず最初に試験するページになることが多いですが、結構な割合でこれに遭遇します。

JIS X 8341-3:2016(WCAG 2.0)における「達成基準2.2.2 一時停止、停止及び非表示」では、下記の引用のように求められています。

動きのある、点滅している、スクロールする、又は自動更新する情報は、次の全ての事項を満たしている。

a) 動き、点滅又はスクロール 動きのある、点滅している、又はスクロールしている情報が、(1)自動的に開始し、(2)5 秒よりも長く継続し、かつ、(3)その他のコンテンツと並行して提示される場合、利用者がそれらを一時停止、停止、又は非表示にすることのできるメカニズムがある。ただし、その動き、点滅、又はスクロールが必要不可欠な動作の一部である場合は除く。
b) 自動更新 自動更新する情報が、(1)自動的に開始し、かつ、(2)その他のコンテンツと並行して提示される場合、利用者がそれを一時停止、停止、若しくは非表示にする、又はその更新頻度を調整することのできるメカニズムがある。

わかりやすく簡単に言えば、自動で動き出して、かつその動きが一定時間以上継続するパーツ、つまり、自動で動作し続けるカルーセルやスライドショー、フェードイン・フェードアウトなどで次々と情報が切り替わっていくようなお知らせやバナー掲載パーツのようなものについては、ユーザーがその動きをコントロールできる仕組み(停止させたり、その部分を非表示したりできるボタンなど)を提供してくださいね。ということです。

カルーセルは少ないスペースで多くの情報を掲載できるので、特にトップページのヘッダ部分などで好まれて採用されることが多いパーツですが、もしページの読み込みなどと同時に自動的に動作をさせる場合は、必ず一時停止ボタンなどとセットで設計・デザインし、実装するようにしましょう。

個人的にはカルーセル自体がそこまで優れたUIだと思っていませんので、あまり積極的にお勧めることもないのですが、どうしてもこのようなパーツを使用する場合、ウェブサイトの要件定義や設計をする方は、「停止ボタンを必ずセットにする」ということを忘れないようにしてください。

ちなみに、クライアント様からアクセシブルなカルーセルってどう実装すればいいの? 的なご質問をいただいた場合に、参考として提示しているのが下記のリポジトリになります。絶対にこの実装が正解などというつもりはありませんが、多少は参考にしていただけるのではないでしょうか(ライブラリとしては Keen-Slider を使用していますが、他のライブラリを使用する場合でもアプローチの仕方として参考にしていただけると思います)。

第4位 キーボードで操作できないUI(達成基準 2.1.1 キーボード / 達成基準 4.1.2 名前(name)、役割(role)及び値(value))

よくあるパターンとしては下記のようなものがあります。

  • アコーディオンUIの開くボタンに相当する部分が、div要素やspan要素、あるいは見出し要素に直接イベントリスナを登録して実装してある(見出しをクリックすると続くセクションが開閉するような実装)
  • スマートフォン向けのメニューなどで、初期状態では閉じられたメニューのサブ階層を開く「+」ボタンなどがspan要素などで実装されている
  • ポインティングデバイスでの操作(マウスオーバー)でしか展開しないサブメニュー(他にナビゲーションの代替手段があれば多少は許容できるが......)
  • ヒントなどをポップアップ表示するような「?」アイコンがdiv要素やspan要素で実装されている、おまけにマウスオーバーでしかヒントが表示されない

私がお仕事で、特に実装を担当される職種の方々(要するにフロントエンドエンジニアと呼ばれるような方々)やウェブアクセシビリティを含めた品質管理を担当される方々に助言などをさせていただく場合、「キーボードでウェブページを操作してみる癖をつけましょう」と必ずお伝えするのですが、マウスでの操作や、タッチデバイスでの実機確認しかしていないと、この問題に気がついていないケースが多いです。

また、「スマートフォン向けのデザインなのにキーボード関係あるんですか?」的な事をおっしゃる方もいますが、一般的なレスポンシブ・ウェブデザイン、CSSのメディアクエリを利用した実装の場合、PCだとしても、ブラウザのウィンドウサイズによって、あるいはブラウザで画面を拡大表示していけばその倍率によってはスマートフォン向け(画面サイズが小さいデバイス向け)のデザインで表示されることになります。つまり、スマートフォン向けのデザインで表示されている時はキーボード操作など関係ない、と決めつけるのは間違いということです。

解決策は簡単で、このようなUIを実装する場合、その操作(開閉など)に使用するボタン類は、button要素を使用して実装する、そして、マウスオーバー時(:hover)だけでなく、フォーカス時(:focus / :focus-within)にも同様の動作をするように実装するだけです(ポップオーバーなどを表示する場合は、フォーカス時ではなく、フォーカス後にエンターキーの押下など、ユーザーのアクションで処理が実行されるようにしましょう)。

また、例えば、よくある質問と答え(FAQ)の各項目において、質問部分をクリックしたら答え部分をアコーディオンUIで展開して表示したい、といったニーズについては、details要素を使用することで、今どきは JavaScript 不要で実装できますので、そういった知識を持つことも大切です。

details要素については過去に私個人のブログや、GitHubリポジトリでソースコードのサンプルを公開していますのでご参考まで。

第3位 中身が空のボタンやリンク(達成基準 4.1.2 名前(name)、役割(role)及び値(value)/ 達成基準 2.4.4 リンクの目的(コンテキスト内))

第4位で挙げた内容とも多少関連するのですが、例えばメニューのサブ階層を開く「+」ボタンなどをきちんとbutton要素で実装したまではよかったものの、そのbuttou要素の中身が空(疑似要素や背景画像でアイコンが表示されているのでぱっと見はボタンに見えるが)であったり、SVG要素を使用したアイコンや、いわゆるアイコンフォントのライブラリを使用して置き換えられたSVG要素がbutton要素内にあるものの、テキストを含まないので空の状態になっているボタンやリンクというのはよく遭遇します。

特に、スマートフォン向けのデザインなどでよく使用される、いわゆるハンバーガーメニューについては、内容が空の場合が本当に多いです。

具体的には下記のような実装になっているケースですね。

<button class="menu-btn">
  <span></span>
  <span></span>
  <span></span>
</button>

3本線の見た目を作るためにspan要素などが入れられていて、それをCSSで整えているようなパターン。

あるいは下記のような、テキスト情報を持たないSVG要素が単体で入っていたり、アイコンフォント用の(結果的には JavaScript などでSVG要素に置き換えられる)要素のみが入っているようなパターンもあります。

<!-- 中身がSVG要素のみ -->
<button class="menu-btn">
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
    <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
  </svg>
</button>

<!-- 中身がアイコンフォント(この例では Font Awesome)向けの要素のみ -->
<button class="menu-btn">
  <i class="fa-solid fa-bars"></i>
</button>

このような実装をしてしまうと、読み上げ環境などを利用しているユーザーにとっては、そのボタンが何のためにあるものなのかを理解することが難しい、あるいは理解が不可能になってしまいます。必ずbutton要素内には、そのボタンが何をするためのボタンなのかがわかるテキスト情報を含める必要があります。

テキストを含める方法はいくつかありますが、例えば不可視のテキストを入れる方法がひとつ。

<button class="menu-btn">
  <span class="menu-btn-bars" aria-hidden="true">
    <span></span>
    <span></span>
    <span></span>
  </span>
  <span class="sr-only">メインメニューを開く</span>
</button>

アイコンフォントを使う場合は下記のように。

<button class="menu-btn">
  <span aria-hidden="true">
    <i class="fa-solid fa-bars"></i>
  <span>
  <span class="sr-only">メインメニューを開く</span>
</button>

.sr-only に対するスタイルは、例えば下記のようなものを適用して、表示上は見えないようにします。

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

中身がSVG要素だけになる場合は、title要素などを加えてあげればテキスト情報となります。

<button class="menu-btn">
  <svg role="img" aria-label="メインメニューを開く" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
    <title>メインメニューを開く</title>
    <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
  </svg>
</button>

あとは、中身は空のまま、button要素にaria-label属性を付与してラベルを付けてしまうという方法もありますが、どうしてもbutton要素の中にテキストを入れられない場合のみの最終手段とした方がよいと個人的には考えています。

ボタン内にアイコンだけが含まれる、いわゆるアイコンボタンの実装については以前、弊社のナレッジブログでも書いていますので合わせてご参照ください。

ここまでの例はすべてbutton要素を使って説明しましたが、リンク(a要素)の場合も同じで、例えば画像のみがa要素内に含まれている状態で画像に代替テキストが付いてない(alt属性の値が空)状態になっていたり、button要素の例で挙げたのと同様、SVG要素やアイコンフォント用の空の要素だけがa要素内にある、といったケースもよく遭遇しますので、注意してください。

余談ですが、「達成基準 2.4.4 リンクの目的(コンテキスト内))」に関していえば、いわゆる「こちら」リンクに遭遇する頻度も高いです。ソースコード的には下記のようなものですね。

<p>○○の詳細は<a href="./example/">こちら</a></p>

達成基準 2.4.4 に関してだけいうなら、上記のようなリンクテキストでも前後の文脈からリンクの目的が判断できると解釈して「強く」エラーですとは指摘しないケースもあるのですが、できれば、「2.4.9 リンクの目的(リンクのみ)(適合レベル AAA)」を意識していただいて、リンクテキスト単体でもリンクの目的が判断できるものにしていただく方がよいと思います。

ほとんどの場合、「a要素内に入れるテキストをちょっとだけ増やすだけでしょ」というレベルで改善できることが多いですので、ぜひ意識してみてください。

余談ついでに書くと、a要素に関連して「ボタンをa要素で実装するな」問題もあるのですが、長くなるので割愛します。

第2位 入力コントロールにラベルがない(達成基準 3.3.2 ラベル又は説明)

入力コントロールというのは、簡単に言えば入力欄やセレクトメニュー、チェックボックスやラジオボタンなど、ユーザーが何かを入力(選択も含む)するためのフォームパーツのことですが、それにラベルが付いていないということで、フォームのウェブアクセシビリティ試験をやっていてほぼ確実に遭遇するのがこれです。

まず、そもそもの実装方法が良くないというものとして挙げられるのが下記のようなもの。

  • 見た目上はラベルと考えられるテキストはあるがlabel要素が適切に使われていない

こちらについては正しくlabel要素を使う癖をつければほぼ解決すると思います。

<label for="first-name">
  お名前(姓) <span>(必須)</span>
</label>
<input type="text" name="first-name" id="first-name" autocomplete="given-name" placeholder="山田" required>

<label for="last-name">
  お名前(名) <span>(必須)</span>
</label>
<input type="text" name="last-name" id="last-name" autocomplete="family-name" placeholder="花子" required>

もうひとつは、実装者にも問題はありつつも、「これはそもそもの設計やデザインが悪いな」と思われるもの。例えば以下のようなものが挙げられます。

  • input要素にプレースホルダ(placeholder属性)を付けて、それをラベルのように扱っている
  • 入力コントロールとラベルが1対1で対応するように設計・デザインされていない

こちらに関しては実装者あるあるで、設計やデザインがそもそもよくないものを、実装側でなんとか工夫してアクセシブルにしないといけない問題みたいなものがあります。

実装者側に知識があれば、aria-label属性をはじめとしたWAI-ARIAなどをうまく活用してなんとかすることもできるんですが、素直にデザイン通り、見た目だけ実装したりすると、ラベルのない入力コントロールだらけになる可能性が出てきます。

placeholder属性をラベル代わりに使うなどというのは実装者がHTMLの仕様を正しく理解していないだけですので、実装者側にも問題はあるのですが、そもそもはプレースホルダがラベルになっているような設計やデザインを作る方に大きな問題があると思われますし、そこまでひどくなくても、入力コントロールとラベルが1対1になっていない設計やデザインというのは結構頻繁に目にします。

例えば、「氏名」というラベルと思われるデザインの下に、2つの入力欄が分割して置かれている(多分、1つが姓の入力欄で、もう1つが名前の入力欄なんだろうな...... と推察できる)。あるいは、電話番号入力欄が3つに分割されていて、しかしデザイン上のラベルは「電話番号」しかないといったものが挙げられますが、どうでしょう? 特に深く考えず、このような設計やデザインを作っていませんか?

上記のようなデザインをそのまま実装しなければならないとき、実装者は下記のように、なんとかアクセシブルになるよう工夫するわけです。

<div>
  氏名 <span>必須</span>
</div>
<div>
  <input type="text" aria-label="氏名(姓)(必須)" name="first-name" id="first-name" autocomplete="given-name" placeholder="山田" required>
  <input type="text" aria-label="氏名(名)(必須)" name="last-name" id="last-name" autocomplete="family-name" placeholder="花子" required>
</div>

確かに、上記のようにaria-label属性を付与しておけば、読み上げ環境に対してはラベルが提供されるかもしれません。しかし、視覚的にこのフォームを認知している人の中には、『「氏名」の下に入力欄が2つ置いてあったら、姓と名を分けてそれぞれ入力する』という、なんとなく日本だと暗黙の了解のようになっていることが、即座にわからない方もいる可能性を考えた方がよいと思います。

ですので、フォームの設計やデザイン(といってもデザイナーさんはほとんどの場合、設計にそってデザインすると思いますので主に設計する方が重要ですが)する方は、「この入力コントロールのラベルはこれ」とわかりやすくセットになっているかを確認されることをお勧めします。

「3つの分かれた電話番号入力欄、それぞれにラベル付けたら変じゃない?」とお考えの方、「そもそも電話番号入力欄を3つに分ける意味があるのか?」から考えましょう。細かく入力欄を分けられると、使う側からすれば一発でコピペ入力などができず正直なところ面倒くさいですよ。

また、これは見落としがちなのですが、ラジオボタンやチェックボックスなどによって、ある項目に対する選択肢の中から選択してもらうような場合、ラジオボタンやチェックボックスとその直接的なラベル自体は関連付いているが、その一連の選択肢自体が、何の選択肢なのか伝わりにくいというケースもあります。

例えば下記のような例です。

<ul>
  <li>
    <input type="radio" id="contact-type-01" name="contact-type" value="サービスに関するお問い合わせ">
    <label for="contact-type-01">サービスに関するお問い合わせ</label>
  </li>
  <li>
    <input type="radio" id="contact-type-02" name="contact-type" value="IRに関するお問い合わせ">
    <label for="contact-type-02">IRに関するお問い合わせ</label>
  </li>
  <li>
    <input type="radio" id="contact-type-03" name="contact-type" value="取材のご依頼">
    <label for="contact-type-03">取材のご依頼</label>
  </li>
  <li>
    <input type="radio" id="contact-type-04" name="contact-type" value="その他お問い合わせ">
    <label for="contact-type-04">その他お問い合わせ</label>
  </li>
</ul>

このような場合には、例えばfieldset要素や、legend要素を使用して、入力コントロールをグルーピングし、ラベルを付けるといったことが必要です。

<fieldset>
  <legend>お問い合わせ種別</legend>
  <ul>
    <li>
      <input type="radio" id="contact-type-01" name="contact-type" value="サービスに関するお問い合わせ">
      <label for="contact-type-01">サービスに関するお問い合わせ</label>
    </li>
    <li>
      <input type="radio" id="contact-type-02" name="contact-type" value="IRに関するお問い合わせ">
      <label for="contact-type-02">IRに関するお問い合わせ</label>
    </li>
    ...省略...
  </ul>
</fieldset>

フォーム関連では他にも、入力エラーに対するエラーメッセージの内容や提示方法に問題があり、「達成基準 3.3.1 エラーの特定」や「達成基準 3.3.3 エラー修正の提案」に関連して指摘をさせていただくケースも多いです。あわせて注意していただくとよいでしょう。

第1位 フォーカスインジケータが非表示(達成基準 2.4.7 フォーカスの可視化)

ウェブページ内の全部にせよ、一部にせよ、この指摘は恐らくすべてのウェブアクセシビリティ試験で指摘している気がします。それも何か意図的にやっているというより、いわゆる「リセットCSS」的なものを使ったら、そこに * {outline: none;} のような凶悪なスタイルが紛れ込んでいましたといった、気付かないうちにそうなっているというケースが多いです。

まず、ほとんどの場合において、わざわざ outline: none; するようなスタイル指定をしなければ、この達成基準で問題が出ることはそこまで多くありません。

レイアウトや組み合わさった背景色などの都合で「フォーカスインジケータが表示されているのに見えない」という箇所が出てしまう場合はありますが、少なくとも「ウェブページ内にある全部のフォーカス可能要素が、達成基準 2.4.7 に関してエラーです」といった指摘が入ることはないと思いますので、まずはそういったスタイルの指定をしないというのが最低限のスタートラインです。

その上で、ウェブページをキーボード操作で必ず確認し、各部のフォーカスに対してフォーカスインジケータがきちんと表示されているかを確認する癖をつけましょう(キーボード操作による確認作業を習慣にすると、フォーカスインジケータが表示されていない時の不便さとイラつきを実体験できるのでお勧めですよ)。

ちなみに、WCAG 2.2 では、「達成基準 2.4.13 フォーカスの外観(レベル AAA)」 のように、このフォーカスインジケータ自体の見た目に対する達成基準も追加されています。可能であればこの辺も意識した上で、フォーカス時のデザインまで考慮できるとなおよいと思います。


この記事は 「アクセシビリティ Advent Calendar 2024」 初日の記事です。明日以降、多くの方がアクセシビリティに関する投稿をしてくださいますので、興味のある方はリンク先を確認してみてください。

さて、今年も(ウェブ版)アドベントカレンダーの時期がやってきました。この時期がくると年末を感じます。毎年、アドベントカレンダーには何かしら参加するようにしているのですが、今年はちょうど書籍の改訂作業などとかぶって忙しかったためギリギリまで手を付けられず、少し焦りましたけども、なんとか記事を公開できました。

冒頭にも書きましたとおり、弊社ではウェブアクセシビリティ試験をはじめ、ウェブアクセシビリティ対応関連サービスを提供しています。

現在、最も多くのお問い合わせ、ご相談を頂いている弊社のウェブアクセシビリティ対応サービスについて、サービスの全体像をまとめた特設ページをご用意しています。詳しくは下記のリンクよりご確認ください。

また、具体的な要件は決まっていないが、ウェブアクセシビリティ対応などについて相談したいことがある、という企業様向けに、必要な時、必要なタイミングで利用できる「スポット利用」と、月額・定額でコンサルティングを利用できる「月額・定額プラン」から選択してご利用可能な、「オンライン ウェブアクセシビリティ コンサルティング」も提供しておりますので、お気軽にご相談ください。