キーボード操作を意識したHTML/CSSコーディング
この記事は 「Webアクセシビリティ Advent Calendar 2020」 5日目の記事です。
アクセシビリティ Advent Calenderの記事を寄稿するにあたり、少しの工夫であらゆるユーザーに対して優しいWebサイトを作れるようなHTML/CSSコーディングの方法についてまとめました。より多くの人にとって優しい・使いやすいWebサイトを作ることは訪れてくださるユーザーの方々だけでなく、クライアントにとってもユーザーの機会損失を防ぐことができるので多大なるメリットがあります。(よくコードが適当でもデザインが見えていれば良いって意見を聞くけれどそんなことはない)
ただ、アクセシビリティを意識したHTML/CSSコーディングについてのまとめだと内容量が非常に多くなりZennなら記事より本で出したほうがベターになってしまうので、今回は数あるアクセシビリティの視点から「キーボード操作でWebサイトを利用しているユーザー」に対象を絞っていくつかまとめてみました。
キーボード操作でWebサイトを利用しているユーザーというのは次のような方々が考えられます。
- マウスやトラックパッドが故障してしまった方
- スクリーンリーダー(音声ブラウザ)を利用している視覚がい者
- 繊細なカーソル操作が困難な身体障がい者や高齢者
- フォームなどはカーソル操作するよりキーボード操作のほうが効率的だという方
また、最近はスマートフォン用のキーボードも売られていますしデスクトップだけでなくモバイルでもキーボード操作を意識する必要があるように思えます。
クリッカブルな要素をdivやspanで作らない
タブやハンバーガーボタンやモーダルを開くためのボタンなどがdivやspanで実装されているのをよく見かけますが、アクセシビリティ的に良い実装方法とは言えません。
<div id="js-toggle-button" class="toggle-button">
<span class="toggle-button__bar"></span>
<span class="toggle-button__bar"></span>
<span class="toggle-button__bar"></span>
</div>
何が問題なのか?
マウスやトラックパッドで操作するなら特に問題はありませんが、キーボード操作でWebサイトを利用しているユーザーはその要素にフォーカスを当てることができず、選択することができなくなります。
もし、下層ページへの動線がハンバーガーメニューだけの場合、そのような環境で訪れている方は下層ページを見ることができなくなってしまう可能性すらあります。
どう実装したら良いか?
クリッカブルな要素はフォーカスの当たらないdiv
やspan
で実装するのではなく、フォーカスの当たるa
やbutton
で実装するのが良いでしょう。
tabindex属性を付与すればdivやspanにもフォーカスを当てること自体はできますが、それが押された際の挙動はJSのkeydownイベントを一々付与する必要があります。a
やbutton
を利用すればそのような煩わしい作業は必要ありません。
個人的にはボタンはVoiceOverでの読み上げ的にもbutton
を使うことをオススメします。a
で実装するとVoiceOverでは「リンク」と読み上げられますからね。もしもボタンをa
で実装する場合には「ボタン」と読み上げるためにrole="botton"
も付与しておきましょう。
div、a、buttonで実装したボタンの比較。実際にTab移動して頂ければdivで作ったボタンにフォーカスが当たらないことが理解できるかと思います。
リンクやボタン、フォームのinput要素のoutlineを消さない
クリッカブルな要素をクリックやタップで操作する際にフォーカスインジゲータが邪魔になるといった理由で以下のような指定がされているのをよく見かけます。
a,
button,
input {
outline: 0;
}
何が問題なのか?
前項と通じるところはありますが、キーボード操作でWebサイトを利用している方はoutlineを消されるとどこにフォーカスが当たっているのか判別がつかず、Webサイトが利用しにくくなるためです。
どう実装したら良いか?
単純にフォーカスを当てたい要素にoutline: 0
oroutline: none
を指定するのはやめましょう。
ただ、カーソル操作の場合クリック時やタップ時のフォーカスインジゲータが煩わしいってのは確かで、デザイン的にoutline: 0
を指定したい…って人も多いと思います。
現代のCSSでは:focus-visible
という便利な擬似クラスが登場していて、これは「キーボード操作でフォーカスされた際」の指定をするものです。これを利用すれば:focus:not(:focus-visible)
とすることで「キーボード操作"以外"でフォーカスされた際」の指定をすることができるのです。
つまり、キーボード操作の場合はoutlineを表示するけどカーソル操作の場合はoulineを消したいって場合には以下のルールセットをベースのCSSなどに指定しておけば実現できます。
:focus:not(:focus-visible) {
outline: 0; /* キーボード操作"以外"でフォーカスされた際はoutlineを消す */
}
ただ、Can I use...を見て頂ければ分かるとは思いますが、IEはともかくSafariでも現在は未実装のため、実用性的にはイマイチなところはあります。
なので、IEやSafari含めて:focus:not(:focus-visible)
を使いたい場合はfocus-visibleのPolyfillを導入しましょう。
手っ取り早く導入するなら、以下のCDNをhead
内で読み込み、
<script src="https://cdn.jsdelivr.net/npm/focus-visible@5.2.0/dist/focus-visible.min.js" integrity="sha384-xRa5B8rCDfdg0npZcxAh+RXswrbFk3g6dlHVeABeluN8EIwdyljz/LqJgc2R3KNA" crossorigin="anonymous" defer></script>
以下のルールセットをベースのCSSなどに指定しておけば完了です。
.js-focus-visible :focus:not(.focus-visible) {
outline: 0;
}
とても簡単。
フォーカスインジゲータがoutlineだけだと見にくい場合もあるので、outline以外でもフォーカスが当たっていることを強調させたほうが良い気がします。
インタラクティブな要素にはhoverが指定されていることが多いので、その指定をフォーカス時にも流用させてあげましょう。
.my-button:hover,
.my-button:focus {
/* ホバーおよびフォーカスが当たった際のスタイル */
}
チェックボックスやラジオボタンを装飾する時にinputをdisplay: noneしない
チェックボックスやラジオボタンのデザインを調整するために元となるinput[type="checkbox"]
orinput[type="radio"]
を非表示にして、それと紐付けたlabel要素やlabel内のspan要素などでチェックボックスやラジオボタンを作るのはWeb制作の現場ではよくあることですが、inputの非表示にdisplay: none
を指定するのはやめておきましょう。
<label class="my-checkbox">
<input class="my-checkbox__input" type="checkbox">
<span class="my-checkbox__icon"></span>
<span class="my-checkbox__text">サンプル</span>
</label>
.my-checkbox__input {
display: none;
}
.my-checkbox__input + .my-checkbox__icon {
/* 自作チェックボックスのスタイル */
}
何が問題なのか?
display:none
されているinputはTabキーでのフォーカスが不可能になってしまうため、キーボード操作でWebサイトを利用している方はそのチェックボックスやラジオボタンを選択することができなくなってしまいます。
labelにtabindex
属性つければいいのでは?と思いがちですが、display: none
されたinputにチェックつけること自体ができないんですね。(JS使えばなんとかなるかもしれない)
どう実装したら良いか?
display: none
やvisibility: hidden
で非表示にするのではなく、キーボードでのフォーカスが可能になるような隠し方をしましょう。
具体的には
-
opacity: 0;
で透明化する -
position: absolute
とleft: 100vw
で画面外に追い出す -
z-index
で後ろのほうに隠す
など。
オススメなのがa11y css resetで提供されている、視覚的には非表示にしてスクリーンリーダー向けに隠しテキストを実装するためのルールセットを流用する方法です。
.my-checkbox__input {
/**
* a11y-css-resetから引用
* https://github.com/mike-engel/a11y-css-reset
**/
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
スクリーンリーダー向けに隠しテキストを実装する場面は僕の環境だとそこそこ多いので、ユーティリティクラスに追加しておくと良いかもしれませんね。
装飾したチェックボックスやラジオボタンのフォーカスは可視化しておく
前項のチェックボックスやラジオボタンを装飾するテクニックは多くのブログ記事でやり方が紹介されてはいますが、残念ながらそのうちの多くを真似してもフォーカスが可視化されないことが多く、ボタンにoutline: 0
を指定しているのと同じような問題が起こっている場合が多いです。(そもそもdisplay: none
やっている文献が多いですが、これは自分も過去に通った道なので責めることはできません)
何が問題なのか?
以上のサンプルをTab移動しても、Spaceキーで選択できることは確認できてもフォーカスが当たっていないのでどこを選択しているのかわからないですよね?
どう実装したら良いか?
非表示にしたinput[type="checkbox"]
orinput[type="radio"]
がフォーカスされているかを見て、隣接セレクタか直下セレクタを使って自作のチェックボックスやラジオボタンにフォーカスが当たったときのスタイル指定を行いましょう。
<label class="my-checkbox">
<input class="my-checkbox__input" type="checkbox">
<span class="my-checkbox__icon"></span>
<span class="my-checkbox__text">フォーカス時にはここに下線を表示したい</span>
</label>
.my-checkbox__input:focus-visible ~ .my-checkbox__text {
text-decoration: underline;
}
/* 前述した:focus-visibleのPolyfillが導入されている場合 */
.my-checkbox__input.focus-visible ~ .my-checkbox__text {
text-decoration: underline;
}
前述した「キーボード操作でフォーカスされた際」の指定ができる:focus-visible
擬似クラスを使ってキーボード操作時のinputのフォーカスの有無を見ています。:focus
を使っていない理由はカーソル操作でチェックした後もフォーカス時のスタイルが残るからです…。そこが気にならないのなら:focus
を使っても問題ないです。
上記のサンプルが個人的なアクセシビリティを意識した自作チェックボックスの結論です。ラジオボタンも同じように実装すれば問題ないはず。改善点があればDiscussionにてお願いします。
モーダルは「Micromodal.js」を使って実装する
以前ツイートしました。
Web制作においては自作できるところはプラグイン使わずに実装したほうが良いって風潮があるように思えますが(自分の周りだけかも)、正直言ってアクセシビリティ面だけを見ても自作で実装するのはコストが高すぎる印象です。
ICS MEDIAの池田さんのツイートの通りですが、モーダルの実装は見るところが非常に多いです。
そういった事情から、自作するより既存の高性能なモーダルプラグインを使って実装したほうが良いというのが個人的な結論です。「Micromodal.js」使ってモーダル作りましょう。
■参考記事:Web制作者は要チェック!モーダルダイアログをアクセシブルに実装する超軽量スクリプト -Micromodal.js | コリス
CSSは自分で書く必要がありますが、キーボード操作面の課題である「閉じるボタンを押さずともEscで一発で閉じられる」「モーダル外はTab移動の対象から外す」も含めてアクセシビリティの問題はクリアできているので導入しない理由がないですね。後はjQuery非依存なのも嬉しいところです。
まとめ
Webサイトをリリースする前に表示確認に加えてTab操作を行ってキーボード操作での不自由がないかは確認しておきましょう。
見落としている箇所多そうなので随時何か項目を追加するかもしれません。
Discussion
「チェックボックスやラジオボタンを装飾する時にinputをdisplay: noneしない」の対策は、今なら、これでもいいかもしれませんね。