【アクセシビリティ】アクセシビリティを意識したスイッチボタンの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/827a101a-6d9d-91ef-6692-ae413da39d18.png)

みなさんアクセシビリティを意識して開発できていますか？

必要なところに`role属性`を記述したり、`tabキー`でフォーカスができるようにしたりなど、意識しないといけないことも多いです。
そのため、アクセシビリティを完璧にやろうとするのは一苦労です。

ただ、コンポーネントごとに区切って、アクセシビリティを理解しておけば、実装するタイミングに思い出しやすく、アクセシビリティも意識しやすいと思います。

そのため、この記事では「**スイッチボタン**」に焦点を当てて、アクセシビリティを意識したスイッチボタンの実装方法とスイッチボタンで意識した方がいいアクセシビリティを解説しようと思います。

## アクセシビリティを意識したスイッチボタンの仕様
### ⚪︎ スイッチボタンとは？

スイッチは、ユーザーが二つの値（オン/オフ）を選択できるコンポーネントです。
スイッチはチェックボックスやトグルボタンに似て、スイッチも二進入力として機能します。
スイッチは `オン/オフ` のみに使用できるのに対し、チェックボックスやトグルボタンは三つ目の中間状態をサポートするオプションを選択できます。
チェックボックスが選択できる状態は、`チェックされている/チェックされていない/部分的にチェックされている` を選択できます。
トグルボタンが選択できる状態は、`押されている/押されていない/部分的に押された` を選択できます。

スイッチ、チェックボックス、トグルボタンはすべて二進入力を提供しているため互換性があります。
そのため、視覚的なデザインとセマンティック的に適したコンポーネントを選択する必要があります。

### ⚪︎ キーボードインタラクション

+ **`Spaceキー`**
  + スイッチにフォーカスがある場合、スイッチの状態を変更する
+ **`Enterキー`**（任意）
  + スイッチにフォーカスがある場合、スイッチの状態を変更する

### ⚪︎ WAI-ARIA の役割、状態、プロパティ

+ スイッチには `role="switch"` を設定する
+ スイッチには、以下のいずれかでアクセス可能なラベルを提供する
  + `role="switch"` を持つ要素内に含まれる 可視テキストコンテンツ
  + `role="switch"` を持つ要素に設定された `aria-labelledby` の値で参照される可視ラベル
  + `role="switch"` を持つ要素に設定された `aria-label` 
+ オンの状態のとき、スイッチ要素には `aria-checked="true"` を設定する
+ オフの状態のとき、スイッチ要素には `aria-checked="false"` を設定する
+ スイッチ要素が `input[type="checkbox"]` の場合、`aria-checked` の代わりに `checked` を使用する
+ スイッチのセットが可視ラベルを持つセマンティックなグループの場合、次のいずれかになる
  + スイッチは、グループラベルを含む要素のIDが設定された `aria-labelledby` を持つ `role="group"` を持つ要素に含まれる
  + スイッチのセットは `<fieldset>` 内に含まれ、ラベルは `<legend>` に含まれる
+ スイッチまたはスイッチグループに関する追加のテキスト情報が含まれている場合、スイッチまたはスイッチグループには、説明を含む要素のIDを設定した `aria-describedby` を設定する

## アクセシビリティを意識したスイッチボタンの完成形
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="rNPKQqG" data-editable="true" data-user="sp_degu" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sp_degu/pen/rNPKQqG">
  Spinbutton Pattern Accessibility</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## アクセシビリティを意識したスイッチボタンの作り方
### 1. HTMLを実装する
```sample.html
<fieldset class="container">
  <button
    role="switch"
    aria-label="スイッチ"
    aria-checked="false"
  ></button>
  <button
    role="switch"
    aria-label="スイッチ"
    aria-checked="true"
  ></button>
</fieldset>
```

### ２. CSSを実装する
```sample.css
body {
  background-color: #212529;
  color: #fff;
  display: grid;
  height: calc(100vh - 40px);
  margin: 0;
  padding: 20px 0;
  place-items: center;
  width: 100vw;
}

.container {
  align-items: center;
  backdrop-filter: blur(15px);
  background-blend-mode: luminosity;
  background-color: rgb(128 128 128 / .3);
  border: none;
  border-radius: 18px;
  display: flex;
  gap: 40px;
  justify-content: center;
  padding: 16px;
  position: relative;
  max-width: 300px;
  min-width: 150px;
  width: 100%;
  &::before {
    background: linear-gradient(135deg, rgb(255 255 255 / .4) 0, rgb(255 255 255 / 0) 40%, rgb(255 255 255 / 0) 60%, rgb(255 255 255 / .1) 100%);
    border: 1.4px solid transparent;
    border-radius: 18px;
    content: "";
    inset: 0;
    position: absolute;
    -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
    -webkit-mask-composite: destination-out;
    mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
    mask-composite: exclude;
    z-index: -1;
  }
}

button[role="switch"] {
  border: none;
  border-radius: 32px;
  box-shadow: 1px 1.5px 4px 0px rgba(0, 0, 0, 0.10) inset, 1px 1.5px 4px 0px rgba(0, 0, 0, 0.08) inset, 0px -0.5px 1px 0px rgba(255, 255, 255, 0.25) inset, 0px -0.5px 1px 0px rgba(255, 255, 255, 0.30) inset;
  cursor: pointer;
  height: 36px;
  position: relative;
  width: 62px;
  &::before {
    background-color: #ffffff;
    border-radius: 50%;
    content: "";
    display: block;
    height: 28px;
    position: absolute;
    top: 4px;
    width: 28px;
  }
  &[aria-checked="false"] {
    background: linear-gradient(0deg, rgba(0, 0, 0, .1) 0%, rgba(0, 0, 0, 0.10) 100%), rgba(0, 0, 0, .5);
    background-blend-mode: luminosity, color-burn;
    &::before {
      left: 4px;
    }
  }
  &[aria-checked="true"] {
    background: linear-gradient(0deg, rgba(208, 208, 208, 0.50) 0%, rgba(208, 208, 208, 0.50) 100%), #32D74B;
    background-blend-mode: color-burn, normal;
    &::before {
      right: 4px;
    }
  }
}
```

### 3. JavaScriptを実装する
```sample.js
document.addEventListener('DOMContentLoaded', () => {
  const switchButtons = document.querySelectorAll('button[role="switch"]');
  switchButtons.forEach((switchButton) => {
    switchButton.addEventListener('click', (e) => {
      updateSwitchButton(e.target);
    });
  });
  
  function updateSwitchButton(switchbutton) {
    const currentState = switchbutton.getAttribute("aria-checked") === 'true';
    const newState = String(!currentState);
    switchbutton.setAttribute('aria-checked', newState);
  }
 });
```

## まとめ
この記事では、「**スイッチボタン**」に焦点を当てて、アクセシビリティを意識したスイッチボタンの実装方法とスイッチボタンで意識した方がいいアクセシビリティを解説しました。

ぜひこの記事をストックして、スイッチボタンを実装する時にアクセシビリティについて思い出してもらえると嬉しいです。

Advent Calendar 2023では、他のコンポーネントにも焦点を当てて、アクセシビリティについても解説しているので、ぜひ購読していてください。

https://qiita.com/advent-calendar/2023/degudegu2510

---

最後まで読んでくださってありがとうございます！

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510