9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ちっちゃくアウトプットAdvent Calendar 2023

Day 14

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

Last updated at Posted at 2023-12-13

これはなに?

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

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

対象読者

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

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

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

  • 運動障がい(手や指先が不自由)
  • 利き腕を怪我した
  • マウスの調子が悪い
  • ゲーム機や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/

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

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

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

参考
mozilla.org_キーボードでナビゲート可能な JavaScript ウィジェット

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

mozillaの警告を引用しますと

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

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


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を使って、デフォルトでフォーカスを当てられるようにしましょう
  • デフォルトでフォーカスが当たらない場合は対策をしましょう

参考

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?