41
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【アクセシビリティ】アクセシビリティを意識したタブUIの作り方

Last updated at Posted at 2023-03-23

はじめに

image1.png

みなさんはちゃんとアクセシビリティを意識して開発できていますか?

必要なところに role属性 を記述したり、tabキーでフォーカスが当たるようにしたりなど、意識しないといけないことも多いです。
そのため、完璧にやろうとするのは難しいと思います。

ただ、モーダルUIやタブUIといったコンポーネントごとに区切って、アクセシビリティを理解しておけば、実装するタイミングに思い出しやすくアクセシビリティについて意識しやすいです。

そのため、この記事ではタブUIのコンポーネントに焦点を当てて、
アクセシビリティを意識したタブUIの実装方法とタブUIで意識した方がいいアクセシビリティを解説しようと思います。

※ アクセシビリティの基本については以下の記事をご参照ください。

アクセシビリティを意識したタブUIを実装する

では早速、アクセシビリティを意識したタブUIについて解説していきます。

1. HTMLを実装する

HTMLから実装していきます。

タブUIのHTMLで意識したアクセシビリティのポイントは、以下の通りです。

  1. role属性を明記すること
    • role属性を明記することで、スクリーンリーダーなどでタブやタブパネルであることを読み上げてくれます。
    • 例)
      • タブの<button>をラップしている要素には、role="tablist"を書く
      • タブの<button>には、role="tab"を書く
      • タブのパネルには、role="tabpanel"を書く
  2. aria属性を明記して、tablisttabpanelを紐づけること
    • タブには、タブパネルを制御する役割があり、タブパネルには、どのタブの内容なのかを示す必要があるので、aria属性を明記して、tablisttabpanelを紐づけます。
    • 例)
      • タブの<button>idとタブのパネルのaria-labelledbyに同じ値を書く
      • タブのパネルのidとタブの<button>``aria-controlsに同じ値を書く
  3. 選択している要素と選択していない要素を明確にすること
    • 選択している要素を明確にすることで、どんな人にも選択していることを伝えます。
    • 例)
      • 選択しているタブの<button>aria-selected="true"を書く
      • 選択していないタブの<button>aria-selected="false"を書く
      • 選択していないタブのパネルにはhiddenを書く
  4. tabindexをコントロールすること
    • タブリストでは、←キー→キーを使ってパネルを切り替えるため、選択しているタブだけにフォーカスが当たるようにします。
    • タブパネルにもちゃんとフォーカスが当たるようにします。
    • 例)
      • 選択しているタブに tabindex="0"を書く
      • 選択していないタブに tabindex="-1"を書く
  5. 選択していないタブパネルにhiddenを明記する。
    • 選択していないタブパネルにhiddenを明記することで、表示されなくなります。

これらを意識して描いたHTMLは↓こちらになります。

tab.html
<div class="tab">
  <div role="tablist">
    <button role="tab" id="tabA" aria-controls="panelA" aria-selected="true"  tabindex="0">
      タブA
    </button>
    <button role="tab" id="tabB" aria-controls="panelB" aria-selected="false" tabindex="-1">
      タブB
    </button>
    <button role="tab" id="tabC"aria-controls="panelC" aria-selected="false" tabindex="-1">
      タブC
    </button>
  </div>
  <div role="tabpanel" id="panelA" aria-labelledby="tabA" tabindex="0">
    <p>パネルAを表示しています。</p>
  </div>
  <div role="tabpanel" id="panelB" aria-labelledby="tabB" tabindex="0" hidden>
    <p>パネルBを表示しています。</p>
  </div>
  <div role="tabpanel" id="panelC" aria-labelledby="tabC" tabindex="0" hidden>
    <p>パネルCを表示しています。</p>
  </div>
</div>

codepenで実装したものは↓こちらです。

See the Pen tab ui by でぐぅー | Qiita (@sp_degu) on CodePen.

2. CSSを実装する

タブUIのCSSにおけるアクセシビリティは、コントラストとかに当たるので、詳しく説明するのは省略します。

今回は、↓こんな感じのスタイルを当てました。

sample.css
.tab {
  width: 100%;
  max-width: 400px;
}

div[role="tablist"] {
  display: flex;
}

button[role="tab"] {
  border: none;
  background-color: #576066;
  border-radius: 8px 8px 0 0;
  font-size: 14px;
  font-weight: bold;
  padding: 4px 12px;
}

button[role="tab"][aria-selected="true"] {
  background-color: #FFFFFF;
}

button[role="tab"][aria-selected="false"]:hover {
  background-color: #FFFFFF;
  cursor: pointer;
}

div[role="tabpanel"] {
  background-color: #FFFFFF;
  border-radius: 0 8px 8px 8px;
  padding: 32px 16px;
}

p {
  font-size: 20px;
  font-weight: bold;
  margin: 0;
}

body {
  background-color: #212529;
  display: grid;
  height: calc(100vh - 16px);
  margin: 0;
  padding: 8px;
  place-items: center;
  width: calc(100% - 16px);
}

codepenで実装したものは↓こちらです。

See the Pen tab ui by でぐぅー | Qiita (@sp_degu) on CodePen.

3. タブのボタンを押して、パネルが切り替わるようにする。

  1. 各タブにclickイベントを追加する
    • タブをクリックした時 changeTabs という関数が動くように、それぞれのタブにclickイベントを追加します。
  2. クリックした要素に aria-selected="true"を追加する
    • 一度全部のaria-selectedfalseにします。
    • その後選択した要素だけaria-selectedtrueに設定します。
  3. クリックした要素に紐付いたタブパネルのhiddenを削除する
    • 一度全部のタブパネルをhiddenを設定します。
    • その後選択した要素だけ.removeAttribute("hidden")hiddenを削除します。
sample.js
window.addEventListener("DOMContentLoaded", () => {
  const tabs = document.querySelectorAll('[role="tab"]');
  const tabList = document.querySelector('[role="tablist"]');

  // 各タブに click イベントを追加します
  tabs.forEach(tab => {
    tab.addEventListener("click", changeTabs);
  });
})

function changeTabs(e) {
  const target = e.target;
  const parent = target.parentNode;
  const grandparent = parent.parentNode;

  // タブから現在すべての選択状態を取り除きます
  parent
    .querySelectorAll('[aria-selected="true"]')
    .forEach(t => t.setAttribute("aria-selected", false));

  // このタブを選択されたタブとして設定します
  target.setAttribute("aria-selected", true);

  // すべてのタブパネルを非表示にします
  grandparent
    .querySelectorAll('[role="tabpanel"]')
    .forEach(p => p.setAttribute("hidden", true));

  // 選択されたパネルを表示します
  grandparent.parentNode
    .querySelector(`#${target.getAttribute("aria-controls")}`)
    .removeAttribute("hidden");
}

codepenで実装したものは↓こちらです。

See the Pen tab ui by でぐぅー | Qiita (@sp_degu) on CodePen.

4. タブをキーボードで操作できるようにする

  1. 最初に表示させるタブを指定します。
    • どのタブを表示しているかを制御するため、tabFocus を定義し、初期値を 最初に表示させるタブを指定します。
  2. キーが押されたことを検知する
    • addEventListenerkeydownでキーボードが押されていることを検知します。
    • のkeyCodeは37で、のkeyCodeは39なので、if分を使って条件を分岐させます。
    • キーが押された、表示されているタブにフォーカスが当たらないようにします。
  3. キーが押した時の処理を書く
    • キーが押されたら、tabFocusの値を1減らします。
      • 表示されているタブが最初のタブ(tabFocusが0以下)なら、最後のタブにフォーカスが当たるようにします。
    • キーが押されたら、tabFocusの値を1増やします。
      • 表示されているタブが最後のタブ(tabFocusがtabs.length以上)なら、最初のタブにフォーカスが当たるようにします。
  4. tabFocusの値に合わせて、フォーカスが当たるようにする
    • tabFocusの値に合わせ、tabinde="0"を指定して、フォーカスが当たるようにします。
sample.js
window.addEventListener("DOMContentLoaded", () => {
  const tabs = document.querySelectorAll('[role="tab"]');
  const tabList = document.querySelector('[role="tablist"]');

  // tabListの中で表示されているタブを指定するようの定数。
  let tabFocus = 0;
  tabList.addEventListener("keydown", e => {
    //← →を押したら
    if (e.keyCode === 37 || e.keyCode === 39) {
      tabs[tabFocus].setAttribute("tabindex", -1);

      if (e.keyCode === 37) {
        // ← を押したら
        tabFocus--;
        // 最初にいる場合は、最後に移動します
        if (tabFocus < 0) {
          tabFocus = tabs.length - 1;
        }
      } else if (e.keyCode === 39) {
        // → を押したら
        tabFocus++;
        // 最後にいる場合は、最初に移動します
        if (tabFocus >= tabs.length) {
          tabFocus = 0;
        }
      }
      tabs[tabFocus].setAttribute("tabindex", 0);
      tabs[tabFocus].focus();
    }
  })
})

// ...以下同じ。

codepenで実装したものは↓こちらです。

See the Pen tab ui by でぐぅー | Qiita (@sp_degu) on CodePen.

5. 完成

完成したのが↓こちらになります。

See the Pen tab ui by でぐぅー | Qiita (@sp_degu) on CodePen.

まとめ

この記事ではタブUIのコンポーネントに焦点を当てて、アクセシビリティを意識したタブUIの実装方法とタブUIで意識した方がいいアクセシビリティを解説しました。

このタブUIのアクセシビリティは、できていないサイトが多いので、ぜひやっていきたいです。
(Qiitaは順次反映させていきます。)

ぜひこの記事をストックして、タブUIを実装する時にこの記事・アクセシビリティについて思い出してもらえると嬉しいです。
他のUIのアクセシビリティについても解説していく予定なので、お楽しみにしていてください。


最後まで読んでくださってありがとうございます!

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

41
34
3

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
41
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?