tabキーで選択できるって実は大事なこと

## これはなに？

社内のアクセシビリティの勉強会で書いた記事を、Qiitaの記事にしました。
アクセシビリティの「操作可能」の中でも、tabキーを使ってコンテンツを閲覧・選択・脱出ができるかという話です。

きちんと対応すると、下記のアクセシビリティを達成できます。

* [ガイドライン 2.1 キーボード操作可能](https://waic.jp/docs/WCAG21/#keyboard-accessible)
    * [達成基準 2.1.1: キーボードを理解する](https://waic.jp/docs/WCAG21/Understanding/keyboard.html)
    * [達成基準 2.1.2: キーボードトラップなし を理解する](https://waic.jp/docs/WCAG21/Understanding/no-keyboard-trap.html)
    * [達成基準 2.1.3: キーボード (例外なし) を理解する](https://waic.jp/docs/WCAG21/Understanding/keyboard-no-exception.html)


## 対象読者

- デザイナー
- スタイルのコーディングをするエンジニア
- 記事を書くときにスタイルをいじることがある人

## マウスじゃなくてキー操作だけをする時ってどんな時だろうと考えてみました

何かしらの理由でマウス操作ができない/しづらい

* 運動障がい（手や指先が不自由）
* 利き腕を怪我した
* マウスの調子が悪い
* ゲーム機やTVからアクセスしている
* 端末にマウス・トラックパッドがない
* 手の乾燥でトラックパッドが反応しない
* ホームポジションから手を離したくない

など、個人的には割と日常的に発生しうることだと思いました。

## ざっくり言うとtabキーでそのコンテンツは選べますか？

最初に紹介したアクセシビリティを、誤解を恐れず簡単に噛み砕くと

* 2.1.1
    * キーボード操作で選択できる（フォーカスが当たる）
* 2.1.2
    * キーボード操作で抜け出せる（フォーカスを外したり、モードを抜けたり）
        * 例：モーダルから抜けることができる
* 2.1.3
    * 2.1.1で例外として認められていたコンテンツも対応する

## 対策

### [対策1]適切なHTMLタグを使おう

下記の要素は、デフォルトでtabキーによってフォーカスを当てることができます。

- href属性の指定されたa要素・link要素
- button要素（disabledでない場合）
- input要素（type属性値がhiddenでなく、disabledでない場合）
- select要素（disabledでない場合）
- textarea要素（disabledでない場合）
- draggable属性（ドラッグ可能）が指定されている要素
- 編集可能な要素のうち、親が編集不可能もしくは親がいないもの
- ブラウジング・コンテキストのコンテナである要素（iframe要素など）
- ソート可能なth要素（複数列にまたがらないth要素）

引用:[http://isolaboratory.com/](http://isolaboratory.com/webstudies/html/focus_navigation.html)

### [対策2]デフォルトでtabキーのフォーカスが当てられない時は `tabindex`

デフォルトでtabキーのフォーカスが当てられない時は `tabindex` を使います。

`div` タグや `span` タグに `tabindex` 属性を付与するとtabキーで選択できるようになります。

参考
[mozilla.org_キーボードでナビゲート可能な JavaScript ウィジェット](https://developer.mozilla.org/ja/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets)

:::note warn
tabキーでフォーカスされる順番に気をつけてください
:::

mozillaの警告を引用しますと

>tabindex に正の値を使わないでください。 tabindex に正の値を持つ要素は、ページ上のデフォルトのインタラクティブ要素の前に配置されます。 つまり、ページ作成者は、tabindex に 1 以上の正の値を使用する時は必ず、ページ上の全てのフォーカス可能要素に対して tabindex の値を設定（および維持）する必要があります。


### 例:Reactで実装されたマテリアルデザインのDrawer

https://material-ui.com/components/drawers/#drawer

```javascript

import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import Divider from '@material-ui/core/Divider';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import InboxIcon from '@material-ui/icons/MoveToInbox';
import MailIcon from '@material-ui/icons/Mail';

const useStyles = makeStyles({
  list: {
    width: 250,
  },
  fullList: {
    width: 'auto',
  },
});

export default function TemporaryDrawer() {
  const classes = useStyles();
  const [state, setState] = React.useState({
    top: false,
    left: false,
    bottom: false,
    right: false,
  });

  const toggleDrawer = (anchor, open) => (event) => {
    if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
      return;
    }

    setState({ ...state, [anchor]: open });
  };

  const list = (anchor) => (
    <div
      className={clsx(classes.list, {
        [classes.fullList]: anchor === 'top' || anchor === 'bottom',
      })}
      role="presentation"
      onClick={toggleDrawer(anchor, false)}
      onKeyDown={toggleDrawer(anchor, false)}
    >
      <List>
        {['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
          <ListItem button key={text}>
            <ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
            <ListItemText primary={text} />
          </ListItem>
        ))}
      </List>
      <Divider />
      <List>
        {['All mail', 'Trash', 'Spam'].map((text, index) => (
          <ListItem button key={text}>
            <ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
            <ListItemText primary={text} />
          </ListItem>
        ))}
      </List>
    </div>
  );

  return (
    <div>
      {['left', 'right', 'top', 'bottom'].map((anchor) => (
        <React.Fragment key={anchor}>
          <Button onClick={toggleDrawer(anchor, true)}>{anchor}</Button>
          <Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
            {list(anchor)}
          </Drawer>
        </React.Fragment>
      ))}
    </div>
  );
}
```

とくにここを見てほしいです :eyes: 
わざわざTabキーで操作できるように追記してあります。

```
    if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
      return;
    }
```

## まとめ

* 実装するときにはtabキーで閲覧・選択・脱出ができるか確認しましょう
* 適切なHTMLを使って、デフォルトでフォーカスを当てられるようにしましょう
* デフォルトでフォーカスが当たらない場合は対策をしましょう

# 参考

* [これでわかる！　Webアクセシビリティって？　JIS X 8341-3って？](https://waic.jp/wp-content/uploads/2018/05/201607_waic_seminar_ueki.pdf)
* [Web Content Accessibility Guidelines (WCAG) 2.1](https://waic.jp/docs/WCAG21/)
* [mozilla.org_キーボードでナビゲート可能な JavaScript ウィジェット](https://developer.mozilla.org/ja/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets)
* [マテリアルUI](https://material-ui.com/components/drawers/#drawer)
* [Focus Navigation](http://isolaboratory.com/webstudies/html/focus_navigation.html)