この記事は『WAI-ARIA対応のタブ型UIを実装する方法』の続きです。
WAI-ARIAはアクセシビリティーの改善に役立つと先の記事で紹介しました。この記事ではWAI-ARIAに対応したVue.jsでのタブのユーザーインターフェイスを解説します。
サンプルをGitHubにアップしているので、デモとソースコードをご覧ください。
前提として、@vue/cliで環境構築したものとします。@vue/cliを使っていなくても、記事「webpack入門(Vue.jsのサンプル付き)」で紹介しているようにwebpack等で環境構築されていても構いません。
Vue.jsでのステート管理
選択されたタブのIDをプロパティtab
に保持することとします。
▼App.vueファイルの抜粋
export default {
data() {
return {
tab: "panel1"
};
},
//
// (一部省略)
//
};
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.vue」を参照ください。
HTMLの実装
HTMLの実装を紹介します。
▼App.vueファイルの抜粋
<template>
<div>
<h1>アクセシビリティー対応バージョン</h1>
<ul role="tablist">
<li role="presentation">
<button
aria-controls="panel1"
role="tab"
v-bind:aria-selected="tab === 'panel1' ? 'true' : 'false'"
v-on:click="handleClick"
>
カベルネ・ソーヴィニョン
</button>
</li>
<li role="presentation">
<button
aria-controls="panel2"
role="tab"
v-bind:aria-selected="tab === 'panel2' ? 'true' : 'false'"
v-on:click="handleClick"
>
メルロー
</button>
</li>
<li role="presentation">
<button
aria-controls="panel3"
role="tab"
v-bind:aria-selected="tab === 'panel3' ? 'true' : 'false'"
v-on:click="handleClick"
>
ピノ・ノワール
</button>
</li>
</ul>
<div
id="panel1"
aria-labelledby="tab1"
v-if="tab === 'panel1'"
role="tabpanel"
class="panel"
>
カベルネ・ソーヴィニョンはブドウの一品種。赤ワインの中でも渋くて重い味わいが特徴です。
</div>
<div
id="panel2"
aria-labelledby="tab2"
v-if="tab === 'panel2'"
role="tabpanel"
class="panel"
>
メルローはブドウの一品種。味はカベルネ・ソーヴィニョンほど酸味やタンニンは強くなく、芳醇でまろやかで繊細な味わいです。
</div>
<div
id="panel3"
aria-labelledby="tab3"
v-if="tab === 'panel3'"
role="tabpanel"
class="panel"
>
ピノ・ノワールはブドウの一品種。カベルネ・ソーヴィニョンと対照的で比較的軽口な味わいです。
</div>
</div>
</template>
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.vue」を参照ください。
HTMLコーディングのポイントとしては次の通り。
- 期待どおりに読み上げられるように
role
属性を適切に利用します - タブとして機能するように、
ul
要素にrole="tablist"
、 タブ部分となるbutton
要素にrole="tab"
、 パネル部分のdiv
要素にrole="tabpanel"
を追加します- 慣習にしたがって
ul>li
でマークアップしましたが、読み上げの支障となるのでli
タグにはrole="presentation"
を指定してます(もしかしたらタブUIにul>li
を使う必要はないかもしれません)
- 慣習にしたがって
- タブ側のボタンは
a
要素ではなくbutton
要素を使ってます。macOS Safariだとa[href="任意"]
要素はデフォルトでタブキーで操作できないためです。Safariでは次のように「Tabキーを押したときにWebページ上の各項目を強調表示」を選択すると、a要素もタブキーで選択可能になります。ただ、このオプションを設定していなくてもタブキーで選択されたほうが望ましいと考えてのことです
JavaScriptとVue.jsに絡んでくるポイントは次の通り。
- タブとなる
button
要素とパネルのdiv
要素の関連性を示すためaria-controls
属性を指定します。値は任意でid
属性を指定します button
要素にタブの選択状態を伝えるために、aria-selected
を真偽値で指定します- Vue.jsのプロパティの値で動的とします。こうすれば半自動的に
aria-selected
属性が切り替わります - Vue.jsの場合は、
aria-selected
を真偽値は文字列でtrue
とfalse
を設定しなければなりません。真偽値のfalse
だとaria-selected
属性自体が外れてしまうからです
- Vue.jsのプロパティの値で動的とします。こうすれば半自動的に
ボタン要素のイベントハンドラー
ボタン要素のイベントハンドラーのコードを紹介します。ボタンとパネルの紐付けは、意味的に合致している aria-controls
属性を利用してます。JavaScriptの制御が必要なものは独自の変数ではなく、可能な限り aria-*
属性で代替するのがベターなやり方と思います。
▼App.vueファイルの抜粋
export default {
data() {
return {
tab: "panel1",
};
},
methods: {
/**
* クリックしたときのイベントハンドラーです。
* @param event イベントオブジェクトです。
* @private
*/
handleClick(event) {
// イベント発生源の要素を取得
const element = event.currentTarget;
// aria-controls 属性の値を取得
const tabState = element.getAttribute("aria-controls");
// プロパティーを更新
this.tab = tabState;
},
},
};
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.vue」を参照ください。
CSSの実装
CSSはなるべく class
属性を使わず、aria-*
属性をセレクターとして指定しています。こうすれば、余計なクラス属性を増やす必要がなくなります。
▼ app.component.cssの抜粋
/* UI制御のための指定 */
[aria-selected="true"] {
background-color: royalblue;
color: white;
}
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.vue」を参照ください。
CSSの実装はCodeGridの記事「WAI-ARIAを活用したフロントエンド実装」で紹介されている「aria属性をCSSセレクターとして利用する」「独自に名前を付けるくらいなら、意味的に合致するaria属性を利用して、アクセシビリティーを確保しましょう」の提案をアイデアとしています。
まとめ
Vue.jsで実装する場合はタブの状態はいずれかのプロパティで管理しているはずです。その値を間借りして aria-*
属性に適用すれば、簡単にアクセシビリティーを向上できます。今回はタブ型UIで紹介しましたが、これは一例に過ぎません。さまざまなユーザーインターフェイスに利用できるので応用くださいませ。
昨年の記事「脱jQueryのためにしたこと」でも紹介したように、AngularやVue.js等のJSライブラリとWAI-ARIAの相性は抜群です。ほんの少しWAI-ARIAの理解が進めば、Vue.jsユーザーの皆さんは簡単に利用できるでしょう。私個人としても多くのプロダクトで積極的にWAI-ARIAを実装しています(パブリックな事例としては「ICS MEDIA」、「Beautifl」など)。この記事によって、音声読み上げを求めているエンドユーザーへの配慮が少しでも進めばと考えています。
この方法はAngularやReactでも実装できるので、次の記事で紹介しています。ぜひご利用ください。
補足
記事を作成するにあたり複数のサンプルを用意して音声読み上げソフト(macOSの「VoiceOver」や「NVDA日本語版」)で検証しました。
※この記事が公開されたのは5年前ですが、2年前の2022年11月に内容をメンテナンスしています。