【アクセシビリティ】アクセシビリティを意識したグリットの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/7483e558-9204-174e-630e-25339fc49b2e.png)

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

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

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

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

## アクセシビリティを意識したグリットの仕様
### ⚪︎ グリットとは？
グリットウィジェットは、`矢印キー`・`Homeキー`・`Endキー` を使用して、グリットウィジェットに含まれる情報や要素をナビゲーションできるものです。

グリットウィジェットは、チェックボックスのコレクションやナビゲーションリンクのグループ化する時や、高機能のスプレッドシートアプリケーションを作成する時にも使用できます。
また、WAI-ARIA属性や支援技術で、グリッドロールを持つ要素の論理構造を説明、表示する時に `行 (row)`・`列 (column)` という言葉をつかいますが、視覚的な表示が必ず表形式でなければならないわけではありません。

Table要素ではなく、グリットウィジェットにする時は、以下の項目を注意しないといけません。

+ 常に複数のフォーカス可能な要素を含んでいること
+ グリットウィジェットに含まれるフォーカス可能な要素のうち１つだけが、Tabフォーカスを受ける
  + Tableの場合は、フォーカス可能な要素、全部にTabフォーカスを受ける
+ フォーカスの移動ができるように開発すること

またグリットウィジェットは、以下の2つの使い方があります。

+ **データグリッド**：表形式で情報を提供する時に使う
  + データグリッドは、列のタイトル、行のタイトル、またはその両方を持つ表形式で情報を提供するために使用する
  + 表形式の情報が編集可能な情報やインタラクティブな情報の場合に役立つ
+ **レイアウトグリッド**：他のウィジェットをグループ化する時に使う
  + レイアウトグリッドは、リンク、ボタン、チェックボックスなどのインタラクティブな要素をグループ化するために使用する
  + 大量のデータセットから要素を動的に読み込む場面で役立つ
  + データグリッドと異なり、列のタイトル、行のタイトルが含まれないことがあり、単一の行のみの場合や単一の列のみの場合もある

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/c0308460-9558-20e6-847b-6fecf25ee20c.png)


### ⚪︎ キーボードインタラクション
#### データグリッド

+ **`→　キー`**
  + フォーカスを右に1つ移動させる
  + フォーカスが一番右の場合、フォーカスは移動しない
+ **`←　キー`**
  + フォーカスを左に1つ移動させる
  + フォーカスが一番左の場合、フォーカスは移動しない
+ **`↓　キー`**
  + フォーカスを下に1つ移動させる
  + フォーカスが一番下の場合、フォーカスは移動しない
+ **`↑　キー`**
  + フォーカスを上に1つ移動させる
  + フォーカスが一番上の場合、フォーカスは移動しない
+ **`Page Downキー`**
  + 任意の行数分、下にフォーカスを移動させる
  + 通常の場合、現在表示されている一番下の行が一番上の行になるようにスクロールする
  + フォーカスが一番下の行にある場合、フォーカスは移動しない
+ **`Page Upキー`**
  + 任意の行数分、上にフォーカスを移動させる
  + 通常の場合、現在表示されている一番上の行が一番下の行になるようにスクロールする
  + フォーカスが一番上の行にある場合、フォーカスは移動しない
+ **`Homeキー`**
  + フォーカスを含む行の最初のセルに移動する
+ **`Endキー`**
  + フォーカスを含む行の最後のセルに移動する
+ **`Controlキー + Homeキー`**
  + 最初の行の最初のセルにフォーカスを移動する
+ **`Controlキー + Endキー`**
  + 最後の行の最後のセルにフォーカスを移動する
+ データグリッドがセル、行、列を選択できる場合、以下のキーが使える
  + **`Controlキー + Spaceキー`**
    + フォーカスを含む列を選択する
  + **`Shiftキー + Spaceキー`**
    + フォーカスを含む行を選択する
  + **`Controlキー + Aキー`**
    + すべてのセルを選択する
  + **`Shiftキー +  →キー`**
    + 選択範囲を右に1つ拡張する
  + **`Shiftキー + ←キー`**
    + 選択範囲を左に1つ拡張する
  + **`Shiftキー + ↓キー`**
    +  選択範囲を下に1つ拡張する
  + **`Shiftキー + ↑キー`**
    +  選択範囲を上に1つ拡張する
+ グリッドのキー操作を無効にして、セル内のコンポーネントをキー操作する場合
  + **`Enterキー`**
    + グリッドのキー操作を無効にして、次の動作を行う
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
      + → テキストボックスが単一行の場合、`Enterキー` で、グリッドのナビゲーション機能を復元するか、隣接するセルの入力フィールドにフォーカスを移動する
    + セルに1つ以上のウィジェットが含まれている場合、フォーカスを最初のウィジェットに移動する
  + **`F2キー`**
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
      + → テキストボックスが単一行の場合、`F2キー` で、グリッドのナビゲーション機能を復元する
    + セルに1つ以上のウィジェットが含まれている場合、フォーカスを最初のウィジェットに移動する
      + → `F2キー` で、グリッドのナビゲーション機能を復元する
  + **`英数字キー`**
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
+ グリッドのナビゲーションが無効になった場合
  + **`Escapeキー`**
    + グリッドのナビゲーション機能を復元する
    + コンテンツが編集されていた場合、変更が元に戻ることもある
  + **`→ キー or ↓ キー`**
    + セルに複数のウィジェットが含まれている場合、フォーカスをセル内の次のウィジェットに移動する
    + それ以外の場合、フォーカスされたウィジェットのキーイベントになる
  + **`← キー or ↑ キー`**
    + セルに複数のウィジェットが含まれている場合、フォーカスをセル内の前のウィジェットに移動する
    + それ以外の場合、フォーカスされたウィジェットのキーイベントになる
  + **`Tab キー`**
    + グリッド内の次のウィジェットにフォーカスを移動する
    + （任意）単一のセル内とグリッド内でフォーカスの移動はお変折り返すことがある
  + **`Shift + Tab キー`**
    + グリッド内の前のウィジェットにフォーカスを移動する
    + （任意）単一のセル内とグリッド内でフォーカスの移動はお変折り返すことがある

<font color="gray">※ セル内にフォーカスを設定するか、セルにフォーカスするかは、セル内の要素に依存するため、[こちらを参照](https://www.w3.org/WAI/ARIA/apg/patterns/grid/#gridNav_focus)。</font>
<font color="gray">※ 上記のキーは、フォーカスをセルからセルに移動させる時に使うため、セル内の要素の操作には使えません。必要な場合は[こちらを参照](https://www.w3.org/WAI/ARIA/apg/patterns/grid/#gridNav_inside)。</font>



#### レイアウトグリッド

+ **`→　キー`**
  + フォーカスを右に1つ移動させる
  + （任意）フォーカスが行の一番右にある場合、次の行の最初のセルに移動する
  + フォーカスが最後のセルの場合、フォーカスは移動しない
+ **`←　キー`**
  + フォーカスを左に1つ移動させる
  + （任意）フォーカスが行の一番左にある場合、前の行の最後のセルに移動する
  + フォーカスが最初のセルの場合、フォーカスは移動しない
+ **`↓　キー`**
  + フォーカスを下に1つ移動させる
  + （任意）フォーカスが列の一番下にある場合、次の列の最初のセルに移動する
  + フォーカスがグリッドないの最後のセルの場合、フォーカスは移動しない
+ **`↑　キー`**
  + フォーカスを上に1つ移動させる
  + （任意）フォーカスが列の一番上にある場合、前の列の最後のセルに移動する
  + フォーカスがグリッドないの最初のセルの場合、フォーカスは移動しない
+ **`Page Downキー`**（任意）
  + 任意の行数分、下にフォーカスを移動させる
  + 通常の場合、現在表示されている一番下の行が一番上の行になるようにスクロールする
  + フォーカスが一番下の行にある場合、フォーカスは移動しない
+ **`Page Upキー`**（任意）
  + 任意の行数分、上にフォーカスを移動させる
  + 通常の場合、現在表示されている一番上の行が一番下の行になるようにスクロールする
  + フォーカスが一番上の行にある場合、フォーカスは移動しない
+ **`Homeキー`**
  + フォーカスを含む行の最初のセルに移動する
  + （任意）単一の列か、1行あたりのセルが3つ未満のグリッドの場合、フォーカスは代わりにグリッドの最初のセルに移動することがあります。
+ **`Endキー`**
  + フォーカスを含む行の最後のセルに移動する
  + （任意）単一の列か、1行あたりのセルが3つ未満のグリッドの場合、フォーカスは代わりにグリッドの最後のセルに移動することがあります。
+ **`Controlキー + Homeキー`**（任意）
  + 最初の行の最初のセルにフォーカスを移動する
+ **`Controlキー + Endキー`**（任意）
  + 最後の行の最後のセルにフォーカスを移動する
+ データグリッドがセル、行、列を選択できる場合、以下のキーが使える
  + **`Controlキー + Spaceキー`**
    + フォーカスを含む列を選択する
  + **`Shiftキー + Spaceキー`**
    + フォーカスを含む行を選択する
  + **`Controlキー + Aキー`**
    + すべてのセルを選択する
  + **`Shiftキー +  →キー`**
    + 選択範囲を右に1つ拡張する
  + **`Shiftキー + ←キー`**
    + 選択範囲を左に1つ拡張する
  + **`Shiftキー + ↓キー`**
    +  選択範囲を下に1つ拡張する
  + **`Shiftキー + ↑キー`**
    +  選択範囲を上に1つ拡張する
+ グリッドのキー操作を無効にして、セル内のコンポーネントをキー操作する場合
  + **`Enterキー`**
    + グリッドのキー操作を無効にして、次の動作を行う
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
      + → テキストボックスが単一行の場合、`Enterキー` で、グリッドのナビゲーション機能を復元するか、隣接するセルの入力フィールドにフォーカスを移動する
    + セルに1つ以上のウィジェットが含まれている場合、フォーカスを最初のウィジェットに移動する
  + **`F2キー`**
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
      + → テキストボックスが単一行の場合、`F2キー` で、グリッドのナビゲーション機能を復元する
    + セルに1つ以上のウィジェットが含まれている場合、フォーカスを最初のウィジェットに移動する
      + → `F2キー` で、グリッドのナビゲーション機能を復元する
  + **`英数字キー`**
    + セルに編集可能なコンテンツが含まれている場合、フォーカスを入力フィールドに移動する
+ グリッドのナビゲーションが無効になった場合
  + **`Escapeキー`**
    + グリッドのナビゲーション機能を復元する
    + コンテンツが編集されていた場合、変更が元に戻ることもある
  + **`→ キー or ↓ キー`**
    + セルに複数のウィジェットが含まれている場合、フォーカスをセル内の次のウィジェットに移動する
    + それ以外の場合、フォーカスされたウィジェットのキーイベントになる
  + **`← キー or ↑ キー`**
    + セルに複数のウィジェットが含まれている場合、フォーカスをセル内の前のウィジェットに移動する
    + それ以外の場合、フォーカスされたウィジェットのキーイベントになる
  + **`Tab キー`**
    + グリッド内の次のウィジェットにフォーカスを移動する
    + （任意）単一のセル内とグリッド内でフォーカスの移動はお変折り返すことがある
  + **`Shift + Tab キー`**
    + グリッド内の前のウィジェットにフォーカスを移動する
    + （任意）単一のセル内とグリッド内でフォーカスの移動はお変折り返すことがある

<font color="gray">※ セル内にフォーカスを設定するか、セルにフォーカスするかは、セル内の要素に依存するため、[こちらを参照](https://www.w3.org/WAI/ARIA/apg/patterns/grid/#gridNav_focus)。</font>
<font color="gray">※ 上記のキーは、フォーカスをセルからセルに移動させる時に使うため、セル内の要素の操作には使えません。必要な場合は[こちらを参照](https://www.w3.org/WAI/ARIA/apg/patterns/grid/#gridNav_inside)。</font>


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

+ グリッドコンテナには、`role="grid"` を設定する
+ 各行のコンテナには `role="row"` を指定し、そのコンテナは、グリッド要素か `role="rowgroup"` が指定された要素の子孫要素に属す
+ 各セルは、row要素の子孫要素か、そのrow要素に属しており、次の `role` を設定する
  + 列のタイトルまたはヘッダ情報を含む場合： `role="columnheader"`
  + 行のタイトルまたはヘッダ情報を含む場合： `role="rowheader"`
  + 行と列のタイトルまたはヘッダ情報を含まない場合： `role="gridcell"`
+ グリッドのラベルとして機能する要素がある場合、`aria-labelledby` にその要素のIDの値を指定する
  + それ以外の場合、`aria-label` を使って、グリッドのラベルを指定する
+ グリッドの説明、キャプションとして機能する要素がある場合、`aria-describedby` にその要素のIDの値を指定する
+ グリッドにソート機能がある場合、ソートされた列 or 行のヘッダセル要素に `aria-sort` を設定する
+ グリッドが選択要素であり、セルが選択されていたら、選択されたセルに `aria-selected="true"` を設定する
  + また、列全部が選択された場合、その列内のすべてのセルに `aria-selected="true"` を設定する
+ グリッドにコンテンツの編集機能があり、ある条件下で一部のセルの編集が無効になる場合、`aria-readonly="true"` を設定する
  + すべてのセルで、編集機能が無効の場合、グリッド要素に `aria-readonly="true"` を設定することがある
  + 編集機能がないグリッドには、`aria-readonly` は設定されない
+ DOMに非表示の行・列か、存在しない行・列がある場合、以下が設定される
  + `aria-colcount` に列の総数、`aria-rowcount` に行の総数を設定する
  + `aria-colindex` `aria-rowindex` にそれぞれのセルの位置を設定する
+ グリッドに複数の行・列にまたがるセルが含まれ、テーブル要素に `role="grid"` 適用されていない場合、`aria-rowspan` と `aria-colspan` を設定する

## アクセシビリティを意識したグリットの完成形
<p class="codepen" data-height="400" data-default-tab="result" data-slug-hash="QWYNaGw" data-editable="true" data-user="sp_degu" style="height: 400px; 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/QWYNaGw">
  Disclosure 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
<table id="ex3-grid" role="grid" aria-labelledby="grid3Label" aria-rowcount="5" aria-colcount="3" class="data" cellspacing="0">
  <tbody>
    <tr data-fixed="true">
      <th>商品</th>
      <th>種類</th>
      <th>値段</th>
    </tr>
    <tr>
      <td tabindex="-1">りんご</td>
      <td tabindex="-1">果物</td>
      <td tabindex="-1">¥200</td>
    </tr>
    <tr>
      <td tabindex="-1">いちご</td>
      <td tabindex="-1">野菜</td>
      <td tabindex="-1">¥400</td>
    </tr>
    <tr>
      <td tabindex="-1">なし</td>
      <td tabindex="-1">果物</td>
      <td tabindex="-1">¥300</td>
    </tr>
    <tr>
      <td tabindex="-1">スイカ</td>
      <td tabindex="-1">野菜</td>
      <td tabindex="-1">¥3000</td>
    </tr>
  </tbody>
</table>
```

### ２. 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;
}

.data {
  background-color: rgb(128 128 128 / .3);
  border-radius: 24px;
  padding: 16px;
  position: relative;
  width: 400px;
  background-blend-mode: luminosity;
  backdrop-filter: blur(50px);
  &::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: 24px;
    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;
  }
}

th {
  font-size: 16px;
  padding: 4px 8px;
  text-align: left;
  & + & {
    border-left: 1px solid rgb(255 255 255 / .2);
  }
}

td {
  border-top: 1px solid rgb(255 255 255 / .2);
  font-size: 16px;
  padding: 4px 8px;
  text-align: left;
  & + & {
    border-left: 1px solid rgb(255 255 255 / .2);
  }
}
```

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

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

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

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

---

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

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

https://twitter.com/degudegu2510