キーボード操作に対応したタブを作るポイントです。
See the Pen Keyboard-navigable tab by webdev (@webdev-jp-net) on CodePen.light
タブ押下をキーボードで実行可能にする
role="tab"
でli
などクリッカブルではない要素もタブにできます。
role
定義により、セマンティック的にはli
をタブとして宣言できるため
スクリーンリーダではタブとして認識させるようになりますが
PCブラウザのキーボード操作ではrole="tab"
要素をa
やbutton
のようにフォーカスしEnter
やSpace
で操作できません。(2020年7月現在)
tabindexでフォーカス可能にしkeydownでclickをdispatchする
要素にtabindex="0"
属性を追加すると、キーボードのtabキーでフォーカスをもたせられるようになります。
これをrole="tab"
とあわせてキーボード操作できるタブUIを実装しようとすると、このようになりますが
このままではタブキーによるフォーカスができるようになっただけで、ボタンやセレクトボックスなどのように、スペースキーを押したらclickイベントが実行できるようにはまだなっていません。
<ul role="tablist"> <li id="tab-a" aria-controls="tabpanel-a" role="tab" tabindex="0" aria-selected="true" >TAB A</li> <li id="tab-b" aria-controls="tabpanel-b" role="tab" tabindex="0" aria-selected="false" >TAB B</li> <li id="tab-c" aria-controls="tabpanel-c" role="tab" tabindex="0" aria-selected="false" >TAB C</li> </ul> <div id="tabpanel-a" aria-labelledby="tab-a" role="tabpanel" aria-hidden="false">tabpanel a</div> <div id="tabpanel-b" aria-labelledby="tab-b" role="tabpanel" aria-hidden="true">tabpanel b</div> <div id="tabpanel-c" aria-labelledby="tab-c" role="tabpanel" aria-hidden="true">tabpanel c</div>
参考:
tabindex – HTML: HyperText Markup Language | MDN
ARIA: Tab Role – Accessibility: Ensuring content is usable by and for everyone | MDN
keydownイベントもaddEventListenerで紐付けておく
role="tab"
要素には、表示を切り替えるためのfunctionをclick
だけでなくkeydown
にも紐付けておくと
キーボードで選択できるようになります。
表示切替イベント内では、イベントタイプでclick
とkeydown
を判別し分岐をいれています。
// clickなら処理を実行 let isValid = e.type === 'click'; const isValidKey = (e.code === "Space" || e.key === "Spacebar" || e.code === "Enter" || e.key === "Enter"); // keydownかつEnterかSpaceなら処理を実行 if((e.type === 'keydown') && isValidKey) { isValid = true; e.preventDefault(); // spaceでの画面スクロールを阻止 } if(isValid) { // タブの切替処理 }
tabキーでのフォーカス移動順を制御
role="tab"
要素をkeydown
イベントで選択した場合には、子要素内の最初のフォーカス可能要素へフォーカスを移動させます。
role="tab"
要素と、紐づくtabpanel
子要素のフォーカス可能な要素をtabキーで移動する場合、このように回遊させます。
role="tab"
要素を選択- 紐づく
tabpanel
子要素でフォーカス可能なものの先頭にフォーカス移動 tabpanel
子要素内の末尾のフォーカス可能なものまでフォーカス移動- 親となる
role="tab"
要素をにフォーカスが戻る
keydownイベントをpreventDefaultで差し止め任意の要素へfocus()させる
tabpanel
子要素でフォーカス可能な末尾の要素でtabキーが押下された場合は
preventDefaultでネイティブのフォーカス移動をキャンセルし
親となるrole="tab"
要素へのfocus()
を設定することで順番を制御できます。
tabキー押下とは反対の、shift + tab キーの遡る操作も同様に設定します。
shift + tab を判定
tabキーとshiftキーが同時に押下されているかは
shiftキーの押下を監視する専用のフラグを設けて判定します。
withShiftKey = false; 要素.addEventListener('keydown', this.setWithShift.bind(this), false); 要素.addEventListener('keyup', this.setWithShift.bind(this), false); setWithShift(e) { if (e.code.match(/Shift/g) || e.key === "Shift") { if(e.type === 'keydown') this.withShiftKey = true; if(e.type === 'keyup') this.withShiftKey = false; } }
keydown
とkeyup
を監視し、keyup
でshift押下フラグwithShiftKey
をfalse
にします。
あとはtabpanel
子要素のフォーカス可能な先頭の要素と末尾の要素へkeydown
イベントを設定し
先頭かつ shift押下中 かつ tab ならば、親となるrole="tab"
にフォーカス移動
末尾かつ shift非押下 かつ tab ならば、親となるrole="tab"
にフォーカス移動
となるよう設定します。
(2020年2月の記事にサンプルソースを追加したリライト)