はじめに
みなさんはちゃんとアクセシビリティを意識して開発できていますか?
必要なところに role属性
を記述したり、tabキーでフォーカスが当たるようにしたりなど、意識しないといけないことも多いです。
そのため、完璧にやろうとするのは難しいと思います。
ただ、モーダルUIやタブUIといったコンポーネントごとに区切って、アクセシビリティを理解しておけば、実装するタイミングに思い出しやすくアクセシビリティについて意識しやすいです。
そのため、この記事ではタブUIのコンポーネントに焦点を当てて、
アクセシビリティを意識したタブUIの実装方法とタブUIで意識した方がいいアクセシビリティを解説しようと思います。
※ アクセシビリティの基本については以下の記事をご参照ください。
アクセシビリティを意識したタブUIを実装する
では早速、アクセシビリティを意識したタブUIについて解説していきます。
1. HTMLを実装する
HTMLから実装していきます。
タブUIのHTMLで意識したアクセシビリティのポイントは、以下の通りです。
- role属性を明記すること
- role属性を明記することで、スクリーンリーダーなどでタブやタブパネルであることを読み上げてくれます。
- 例)
- タブの
<button>
をラップしている要素には、role="tablist"
を書く - タブの
<button>
には、role="tab"
を書く - タブのパネルには、
role="tabpanel"
を書く
- タブの
- aria属性を明記して、
tablist
とtabpanel
を紐づけること- タブには、タブパネルを制御する役割があり、タブパネルには、どのタブの内容なのかを示す必要があるので、aria属性を明記して、
tablist
とtabpanel
を紐づけます。 - 例)
- タブの
<button>
のid
とタブのパネルのaria-labelledby
に同じ値を書く - タブのパネルの
id
とタブの<button>``aria-controls
に同じ値を書く
- タブの
- タブには、タブパネルを制御する役割があり、タブパネルには、どのタブの内容なのかを示す必要があるので、aria属性を明記して、
- 選択している要素と選択していない要素を明確にすること
- 選択している要素を明確にすることで、どんな人にも選択していることを伝えます。
- 例)
- 選択しているタブの
<button>
にaria-selected="true"
を書く - 選択していないタブの
<button>
にaria-selected="false"
を書く - 選択していないタブのパネルには
hidden
を書く
- 選択しているタブの
- tabindexをコントロールすること
- タブリストでは、
←キー
・→キー
を使ってパネルを切り替えるため、選択しているタブだけにフォーカスが当たるようにします。 - タブパネルにもちゃんとフォーカスが当たるようにします。
- 例)
- 選択しているタブに
tabindex="0"
を書く - 選択していないタブに
tabindex="-1"
を書く
- 選択しているタブに
- タブリストでは、
- 選択していないタブパネルに
hidden
を明記する。- 選択していないタブパネルに
hidden
を明記することで、表示されなくなります。
- 選択していないタブパネルに
これらを意識して描いた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におけるアクセシビリティは、コントラストとかに当たるので、詳しく説明するのは省略します。
今回は、↓こんな感じのスタイルを当てました。
.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. タブのボタンを押して、パネルが切り替わるようにする。
- 各タブにclickイベントを追加する
- タブをクリックした時
changeTabs
という関数が動くように、それぞれのタブにclickイベントを追加します。
- タブをクリックした時
- クリックした要素に
aria-selected="true"
を追加する- 一度全部の
aria-selected
をfalse
にします。 - その後選択した要素だけ
aria-selected
をtrue
に設定します。
- 一度全部の
- クリックした要素に紐付いたタブパネルの
hidden
を削除する- 一度全部のタブパネルを
hidden
を設定します。 - その後選択した要素だけ
.removeAttribute("hidden")
でhidden
を削除します。
- 一度全部のタブパネルを
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. タブをキーボードで操作できるようにする
- 最初に表示させるタブを指定します。
- どのタブを表示しているかを制御するため、
tabFocus
を定義し、初期値を 最初に表示させるタブを指定します。
- どのタブを表示しているかを制御するため、
-
←
・→
キーが押されたことを検知する-
addEventListener
のkeydown
でキーボードが押されていることを検知します。 -
←
のkeyCodeは37で、→
のkeyCodeは39なので、if分を使って条件を分岐させます。 -
←
・→
キーが押された、表示されているタブにフォーカスが当たらないようにします。
-
-
←
・→
キーが押した時の処理を書く-
←
キーが押されたら、tabFocus
の値を1減らします。- 表示されているタブが最初のタブ(tabFocusが0以下)なら、最後のタブにフォーカスが当たるようにします。
-
→
キーが押されたら、tabFocus
の値を1増やします。- 表示されているタブが最後のタブ(tabFocusがtabs.length以上)なら、最初のタブにフォーカスが当たるようにします。
-
-
tabFocus
の値に合わせて、フォーカスが当たるようにする-
tabFocus
の値に合わせ、tabinde="0"
を指定して、フォーカスが当たるようにします。
-
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)のフォローをお願いします。