これはなに?
社内のアクセシビリティの勉強会で書いた記事を、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要素)
[対策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>
);
}
とくにここを見てほしいです
わざわざTabキーで操作できるように追記してあります。
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
まとめ
- 実装するときにはtabキーで閲覧・選択・脱出ができるか確認しましょう
- 適切なHTMLを使って、デフォルトでフォーカスを当てられるようにしましょう
- デフォルトでフォーカスが当たらない場合は対策をしましょう