【アクセシビリティ】アクセシビリティを意識したツールバーの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/e8582cdf-eeef-c5de-6bf8-f122f624002c.png)

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

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

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

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

## アクセシビリティを意識したツールバーの仕様
### ⚪︎ ツールバーとは？
ツールバーは、ボタン、メニューボタン、チェックボックスなどの一連のコントロールをグループ化したコンポーネントです。

ツールバーの一部が視覚的にグループ化している場合、`role="toolbar"` を使用して、グループ化していることとその目的をユーザーに伝えることができます。
また、ツールバーをグループ化すると、キーボードユーザーがタブフォーカスの数を減らす効果もあります。

ツールバーの利点を最適化するためには、

+ キーボードのタブフォーカスでツールバー内の1つ要素にフォーカスが当たるようにし、矢印キーでツールバー内のフォーカスを移動させる
  + 水平ツールバーの場合は、`→キー` と `←キー` でフォーカスを移動させる
    + `↑キー` と `↓キー` は、それぞれの機能を提供するか、スピンボタンのようにコンポーネントが必要としている操作を提供する
  + 垂直ツールバーでは、`↑キー` と `↓キー` でコフォーカスを移動させる
    + `→キー` と `←キー` は、それぞれの機能を提供するか、水平スライダーのようにコンポーネントが必要としている操作を提供する
  + 複数の行にフォーカスが必要なツールバーでは、`→キー` と `←キー` で行から行へフォーカスを移動させる
    + `↑キー` と `↓キー` は、ツールバー内のコンポーネント操作のために何も設定しない
+ ツールバーのナビゲーションに使用される矢印キーがコンポーネントの操作に必要にならないようにする
  + 避けられない場合は、そのようなコントロールを1つだけ含めて、それをツールバーの最後の要素にする
+ グループが3つ以上のフォーカスする要素を含む場合、ツールバーをグルーピング要素として使用する

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

+ **`Tabキー`・`Shiftキー・Tabキー`**
  + ツールバーへのフォーカスの移動とツールバーからのフォーカスの移動
  + ツールバーにフォーカスが移動するとき
    + ツールバーに初めてフォーカスが移動する場合、フォーカスは無効でない最初の要素に移動する
    + ツールバーが以前にフォーカスを含んでいた場合、最後にフォーカスを持っていた要素にフォーカスを移動する（任意）
    + それ以外の場合は、無効でない最初の要素にフォーカスを移動する
+ 水平ツールバーの場合（デフォルト）
  + **`←キー`**
    + 前の要素にフォーカスを移動する
    + 最初の要素の場合は、最後の要素にフォーカスを移動する
  + **`→キー`**
    + 次の要素にフォーカスを移動する
    + 最後の要素の場合は、最初の要素にフォーカスを移動する
+ 垂直ツールバーの場合
  + **`↑キー`**
    + 前の要素にフォーカスを移動する
    + 最初の要素の場合は、最後の要素にフォーカスを移動する
  + **`↓キー`**
    + 次の要素にフォーカスを移動する
    + 最後の要素の場合は、最初の要素にフォーカスを移動する
+ **`Homeキー`**（任意）
  + 最初の要素にフォーカスを移動する
+ **`Endキー`**（任意）
  + 最後の要素にフォーカスを移動する

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

+ ツールバーのコンテナーとして機能する要素には、`role="toolbar"` を設定する
+ ツールバーに可視ラベルがある場合、可視ラベルのIDを `aria-labelledby` に設定する
  + 可視ラベルがない場合は、`aria-label` でラベルを提供する
+ コントロールが垂直に配置されている場合、ツールバー要素に `aria-orientation="vertical"` を設定する
  + `aria-orientation` は、`horizontal` がデフォルト

## アクセシビリティを意識したツールバーの完成形
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="zYeaepv" 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/zYeaepv">
  Slider Accessibillity</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
<div role="toolbar" class="container">
  <div class="group">
    <button class="item material-symbols-outlined" tabindex="0">
      format_bold
    </button>
    <button class="item material-symbols-outlined" tabindex="-1">
      format_underlined
    </button>
    <button class="item material-symbols-outlined" tabindex="-1">
      format_italic
    </button>
  </div>
  <div class="group">
    <button class="item material-symbols-outlined" tabindex="-1">
      format_align_left
    </button>
    <button class="item material-symbols-outlined" tabindex="-1">
      format_align_center
    </button>
    <button class="item material-symbols-outlined" tabindex="-1">
      format_align_right
    </button>
  </div>
</div>
```

### ２. 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 {
  backdrop-filter: blur(50px);
  background-color: rgb(128 128 128 / .3);
  background-blend-mode: luminosity;
  border-radius: 50px;
  display: flex;
  gap: 12px;
  padding: 12px;
  position: relative;
  &::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: 32px;
    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;
  }
}

.group {
  display: flex;
}

button {
  border: none;
  border-radius: 50%;
  background: none;
  color: #ffffff;
  padding: 8px;
  
  &:hover, &:focus, &:active {
    background: radial-gradient(34.12% 136.61% at 50% 100%, rgba(94, 94, 94, 0.14) 0%, rgba(94, 94, 94, 0.00) 73.85%), radial-gradient(50% 164.29% at 50% 100%, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.00) 60.33%), linear-gradient(0deg, rgba(94, 94, 94, 0.18) 0%, rgba(94, 94, 94, 0.18) 100%), rgba(255, 255, 255, 0.06);
    background-blend-mode: color-dodge, normal, color-dodge, lighten;
  }
}
```

### 3. JavaScriptを実装する
```sample.js
window.addEventListener("DOMContentLoaded", () => {
  const items = document.querySelectorAll('.item');
  const toolbar = document.querySelector('[role="toolbar"]');
  let itemFocus = 0;
  toolbar.addEventListener("keydown", (e) => {
    if (e.keyCode === 37 || e.keyCode === 39) {
      items[itemFocus].setAttribute("tabindex", -1);
      if (e.keyCode === 37) {
        itemFocus--;
        if (itemFocus < 0) {
          itemFocus = items.length - 1;
        }
      } else if (e.keyCode === 39){
        itemFocus++;
        if (itemFocus >= items.length) {
          itemFocus = 0;
        }
      }
      items[itemFocus].setAttribute("tabindex", 0);
      items[itemFocus].focus();
    } else if (e.keyCode === 36) {
      itemFocus = 0;
      items[itemFocus].setAttribute("tabindex", 0);
      items[itemFocus].focus();
    } else if (e.keyCode === 35) {
      itemFocus = items.length - 1;
      items[itemFocus].setAttribute("tabindex", 0);
      items[itemFocus].focus();
    }
  });
});
```

## まとめ
この記事では、「**ツールバー**」に焦点を当てて、アクセシビリティを意識したツールバーの実装方法とツールバーで意識した方がいいアクセシビリティを解説しました。

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

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

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

---

最後に、普段はHTMLやCSS、アクセシビリティ、デザインといったところを中心に
Qiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510

また、「フロントエンドxデザイン」をテーマにDevトークを募集しているので、
興味がある方は、ぜひお話しましょう。
Qiitaの記事の内容でもアウトプットに関する内容でも構いません。

https://jobs.qiita.com/employers/qiita-inc/dev_talks/404?utm_source=link_share&utm_medium=social&utm_campaign=dev_talk_share&utm_content=dev_talk_show