👌

タブUIをアクセシブルにする

2024/06/09に公開
2

どうも、nano72mknです。
アクセシビリティを意識してタブUIを作ったので、実装時に調べたことやポイントをまとめます。

タブUIについて

まず、初めにタブUIと言われて思い浮かべるのは、この形だと思います。
タブUIの画像

このUIは、2つのパーツに分けることができます。

1つ目は、「タブ」と呼ばれるパーツ
タブパーツ

2つ目は、「タブパネル」と呼ばれるパーツ
タブパネル

この2つのパーツをがっちゃんこして、タブUIは出来ています。

タブUIをアクセシブルにする

roleとaria属性を付与してアクセシビリティ対応をする。

roleを付与する

付与する必要があるものは下記の3つ

  • tab
  • tablist
  • tabpanel

tabとtablist

タブにはtab、複数のタブを囲っている要素にはtablistのroleを付与する
タブにはtab、タブを複数まとめたものにはtablistのroleをつける

<div id="tab">
  <div role="tablist">
    <button role="tab">Tab1</button>
    <button role="tab">Tab2</button>
    <button role="tab">Tab3</button>
  </div>
  ...
<div>

tabpanel

タブパネルにtabpanelのroleをつける
タブパネルにtabpanelのroleをつける

<div id="tab">
  ...
  <div role="tabpanel">Tab Panel 1</div>
  <div role="tabpanel">Tab Panel 2</div>
  <div role="tabpanel">Tab Panel 3</div>
</div>

支援技術に対応する

何のタブなのか、タブの状態はどうなっているかを支援技術に伝えるために、aria属性を付与します

tablistaria-labelをつける

タブにフォーカスした際に何のタブグループなのかを知らせるためのaria-labelを設定します。

<div id="tab">
  <div role="tablist" aria-label="hoge">
    <button role="tab">Tab1</button>
    <button role="tab">Tab2</button>
    <button role="tab">Tab3</button>
  </div>
  ...
<div>

上記の実装で、「Tab1」にフォーカスがあたった際に 「Tab1、選択中、タブ、1/3、hoge、 タブグループ」 と読み上げてくれます。
※ 「hoge」がaria-labelで指定したテキスト

tabaria-selectedをつける

どのタブが選択されているかを判定できるようにするために、aria-selectedを付与します。
また、別のタブを選択したときにaria-selectedの状態を切り替えます。

<div id="tab">
  <div role="tablist" aria-label="hoge">
    <button role="tab" aria-selected="true">Tab1</button>
    <button role="tab" aria-selected="false">Tab2</button>
    <button role="tab" aria-selected="false">Tab3</button>
  </div>
  ...
<div>

tabtabpanelを紐づける

紐づける為にやることが2つあります

  1. tabaria-controlsを指定
  2. tabpanelaria-labelledbyを指定
<div id="tab">
  <div role="tablist" aria-label="hoge">
    <button
      role="tab"
      id="tab-1"
      aria-selected="true"
      aria-controls="tabpanel-1">
      Tab1
    </button>
    ...
  </div>
  <div
    role="tabpanel"
    id="tabpanel-1"
    aria-labelledby="tab-1">
    Tab Panel 1
  </div>
  ...
<div>

aria-labelledbyの影響で、タブパネルにフォーカスを当てたときにも 「本文、Tab1、タブパネル」 とタブのラベルも読み上げてくれます。

tabpaneldisplay: none;をつける

表示されているタブパネル以外にはdisplay: none;を付与し非表示にします。

<div id="tab">
  ...
  <div role="tabpanel">Tab Panel 1</div>
  <div role="tabpanel" style="display: none;">Tab Panel 2</div>
  <div role="tabpanel" style="display: none;">Tab Panel 3</div>
</div>

display: none;を付与しているときは、すでにアクセシビリティツリーから削除されているためaria-hiddenは不要です。

キーボードで操作できるようにする

WCAGの達成基準2.1.1で下記のように言われております。

達成基準 2.1.1 キーボード (レベル A): コンテンツのすべての機能は、個々のキーストロークに特定のタイミングを要することなく、キーボードインタフェースを通じて操作可能である。ただし、その根本的な機能が利用者の動作による終点だけではない軌跡に依存する入力を必要とする場合は除く。

この項目はレベルA(最低基準)なので、対応していきます。
タブUIでクリアするべきキーボードの操作は4つあり、JSで実装します。

tabのフォーカスを左右キーで移動させる

左右の矢印キーを押した際に、タブのフォーカスを移動するようにします。

「Tab1」にフォーカスがあった場合に左キーを押したときには「Tab3」へ
「Tab3」にフォーカスがあった場合に右キーを押したときには「Tab1」へフォーカスが移るようにします。

左右キーでフォーカスを移動している様子

vue.jsであれば、@keydown.right@keydown.leftを使うとサクッとできます。

フォーカスがtablistの外から移動してくる場合、アクティブなtabに移動させる

これを実現するためには、tabindexを調整する必要があります。

tabindexは、-1が指定されていると順次ナビゲーションでは到達できなくなるので、その性質を使います。

<div id="tab">
  <div role="tablist" aria-label="hoge">
    <button role="tab" aria-selected="false" tabindex="-1">Tab1</button>
    <button role="tab" aria-selected="true" tabindex="0">Tab2</button>
    <button role="tab" aria-selected="false" tabindex="-1">Tab3</button>
  </div>
  ...
<div>

アクティブなタブはtabindexを0にし、それ以外はtabindexを-1にしてフォーカスが当たらないようにします。

フォーカスが外の要素からタブに移動する際に、アクティブなタブに移動する様子

外の要素から「Tab1」をスキップし、「Tab2」にフォーカスが移動することを確認できます。

フォーカスがアクティブなtabにある場合、紐づいているtabpanelに移動させる

tabindex="0"を付与するだけで対応できます。

<div id="tab">
  ...
  <div role="tabpanel" tabindex="0">Tab Panel 1</div>
  <div role="tabpanel" tabindex="0">Tab Panel 2</div>
  <div role="tabpanel" tabindex="0">Tab Panel 3</div>
</div>

フォーカスがアクティブなタブから対象のタブパネルに移動する様子

フォーカスが「Tab2」から「Tab3」ではなく、直接紐づいているタブへ移動しているのを確認できます。

おまけ: tabにフォーカスが当たっている時にDeleteキーを押したら削除

tabが削除可能な場合は、Deleteキーで選択されているタブを削除できるようにする必要があります。

まとめ

上記対応を行うと、下記codepenのようになります。

以上が、タブUIをアクセシブルにするためのポイントでした。
UIライブラリを使っていると、わからないような細かい対応もあり、とても楽しく実装できました。

また、何か間違いや足りない対応などがありましたら、お気軽にコメントしていただけると嬉しいです!
最後まで読んでいただきありがとうございました。

参考

サクッと説明しちゃっているので、詳しく知りたい方は、下記参考記事を読んでみてください!

GitHubで編集を提案
スタフェステックブログ

Discussion

しょうた🍊なつみかんしょうた🍊なつみかん

until-foundの存在を初めて知りました!
ページ内検索で引っかけることが出来る事ができるのは便利ですし、
safari等非対応環境でも既存のhiddenと同じ挙動なのも安心ですね

とても、勉強になります!
もっとuntil-foundについて調べてみます!
ありがとうございました🙇