チェックボックスのアクセシビリティ対応をしたお話

この記事はフラー [Advent Calendar 2020](https://adventar.org/calendars/5034) の 7日目の記事です。
6日目は、aikosogabeさんで、[フラーに入社して1ヶ月経った](https://note.com/aikosogabe/n/n09fa790cdd50)でした。

最近、仕事でWebアクセシビリティについて調べる機会があったので、今回はその中でもつい最近調べた、チェックボックスのアクセシビリティについてやったことを書いていこうと思います。


## 前提
仕事で画像のような、クリックするとチェックされるようなカスタムチェックボックスをReactで作っていました。

![スクリーンショット 2020-12-06 17.40.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/720f6d1e-5246-dcbd-cb72-615b5c82b9cc.png)
![スクリーンショット 2020-12-06 17.40.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/fca7a702-6686-ddc2-6f62-a32ad8178962.png)

で、その時作成したコードはだいたいこんな感じでした。
（TypeScriptを使用しているのでtsx形式になっていますが、見る上ではjsxだと思っていただけると......。）

```tsx:Checkbox.tsx
const Checkbox = () => {
  const [isChecked, setIsChecked] = useState(false);

  const handleChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <div className="content">
      <label className={`${isChecked? "checkbox cheked" : "checkbox"}`}>
        <input type="checkbox" checked={isChecked} onChange={handleChange} />
        <span>スイッチ</span>
      </label>
    </div>
  );
};
```

```css:checkbox.css
.content {
  width: 100px;
}
.checkbox {
  display: block;
  border: 2px solid grey;
  text-align: center;
}

.checked {
  background: yellow;
}

input {
  display: none;
}
```

コードをぱっとみても分からない人向けに説明をすると、`Checkbox`はHTMLを返す関数です。
`isChecked`でチェックボックスの状態を管理して、チェックボックスが押されたタイミングで状態を反転させるようにしています。（handleChange）

さて、このチェックボックスにアクセシビリティ対応をすることになったのですが、一言でWebアクセシビリティと言っても「キーボードナビゲーション」や「スクリーンリーダー」など、様々な要素があります。この記事では「スクリーンリーダー」への対応に関して書いていきます。

また、これから紹介する結果は以下のOS、ブラウザで確認しています。
`OS: macOS Catalina バージョン 10.15.7
ブラウザ: Chrome バージョン 87.0.4280.67
スクリーンリーダー: VoiceOver(PC)`

## 対応策
#### display:noneを使用しない
前提の項目で紹介したデモのコードですが、スクリーンリーダーではうまく動作しません。
手元の環境ではspan要素内の文字を読むことまでは確認できましたが、この要素がチェックボックスであることまではスクリーンリーダーで読み取ることができません。

![スクリーンショット 2020-12-06 22.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/fad9feb7-5838-e26f-27e0-01c6f9e92ae3.png)

これはinput要素に `display:none` が設定されているためです。
ある要素に対して `display:none` や `visibility:hidden` が設定されていると、スクリーンリーダーのような支援技術はその要素を認識しなくなります。
そのため、前提の項目で例示したデモのコードでは、input要素そのものがスクリーンリーダーから認識されないので、チェックボックスであるというinput要素そのものが持つ情報も抜け落ちてしまいます。

参考: [コンテンツの非表示と更新](https://developers.google.com/web/fundamentals/accessibility/semantics-aria/hiding-and-updating-content?hl=ja)

`display:none` はもともと、チェックボックスの見た目をカスタマイズするときにinput要素を見えないようにするためのものです。
なので `display:none` を消しただけでは「チェックボックスをカスタマイズする」という目的は達成できません。
以下の画像のようにinput要素が見えてしまいます。
![スクリーンショット 2020-12-06 22.37.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/78513935-df9e-bd1b-9dc7-38c863f4961a.png)

#### opacity: 0でinput要素を視覚的に見えないようにする
次はinput要素をどうやって視覚的に消すかですが、調べた限りではいくつか方法があると思います。
例えば、 `clip: rect(0px, 0px, 0px, 0px)`で可視領域を狭める手法です。
参考: [chakra Checkbox](https://chakra-ui.com/docs/form/checkbox)

なので、ここで使用した手法はあくまでいくつかある実装の一つだと思っています。

ここで使用した手法は `opacity: 0` で視覚的に見えなくするという方法です。
変更はcssだけなのでcssだけ載せます。

```css:checkbox.css
.content {
  width: 100px;
}
.checkbox {
  position: relative; /* 追加 */
  display: block;
  border: 2px solid grey;
  text-align: center;
}

.checked {
  background: yellow;
}

input {
  position: absolute; /* 追加 */
  margin: 0; /* 追加 */
  width: 100%; /* 追加 */
  height: 100%; /* 追加 */
  opacity: 0; /* 追加 */
}
```

`opacity`の他にもいくつかcssを追加しています。
`position`はinputの領域をlabelの領域に重ねるため、`width`、`height`はスクリーンリーダーのカーソルを当てた時の領域を見た目通りにするためです。

以下の画像はcss変更後のチェックボックスですが、前提の項目で紹介したデモとは違い、スクリーンリーダーにチェックボックスと認識されていることが分かります。
![スクリーンショット 2020-12-06 23.13.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/059436e9-eaf7-19be-6de5-d725f892d034.png)

あとはデザインを少し修正して、対応完了です。
![スクリーンショット 2020-12-06 23.18.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540090/e6272431-cbbd-4c42-392f-087780c04b66.png)

## まとめ
今回はスクリーンリーダーにやさしいチェックボックスの一例として自分がやったことをまとめました。
普段はあまり意識しない場所かと思いますが、スクリーンリーダーのことも考えてみるきっかけになればと思います。
それはそれとして、自分でも引き続きWebアクセシビリティについて勉強していこうと思います。