Vue.jsを使用してアクセス可能なオートコンプリートコンポーネントを作成する方法
私たちが構築したこのオートコンプリートコンポーネントを覚えていますか? ほとんどのユーザーはそれを使用できますが、Webを閲覧するために支援技術を必要とする障害を持つ人々はそれを使用できません。 これは、これらのテクノロジーが、コンポーネントが通常の入力以上のものであることを理解するのに十分なセマンティックを作成しなかったためです。
この記事では、ARIA属性を使用して、オートコンプリートを完全にアクセス可能なものにする方法を学習しました。
アクセス可能なリッチインターネットアプリケーション(ARIA)
支援技術を使用してWebを閲覧しようとしたことがありますか? ほとんどのオペレーティングシステムには統合ソリューションが付属しています。MacOSではVoiceOverを開き、cmd + F5
を押すと、WindowsではNarratorをWindows logo key + Ctrl + Enter
を押すと起動できます。 ]。
上記のいずれかをこのオートコンプリートコンポーネントで使用すると、オートコンプリートがテキストフィールドであり、オプションのリストについて通知されないことがわかります。
ARIA属性を使用してこれを変更できます。 ARIA仕様は、支援技術ソフトウェアがコンテンツのセマンティクスを理解できるようにする一連の属性を提供することにより、障害を持つ人々がWebコンテンツを使用できるようにする方法を定義しています。
ラベルは重要です
シンプルなラベルがどれだけ使いやすさを向上させることができるかに驚かれることでしょう。
コンポーネントをアプリケーションにすばやくセットアップし、VoiceOverを使用して操作してみましょう。
app.vue
<template> <div id="app"> <div> <label>Choose a fruit:</label> <autocomplete :items="[ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']" /> </div> </div> </template>
VoiceOverがコンポーネントと対話できるようにすると、テキストフィールドの存在のみが認識されますが、支援技術によってラベルが取得されないため、テキストフィールドが何のためにあるのかわかりません。
aria-label
またはaria-labelledby
属性のいずれかを追加することにより、ユーザーはこの入力の目的を知ることができます。
aria-labelledby属性のオートコンプリートに小道具を追加しましょう。 代わりにaria-label
を提供することを選択できますが、ほとんどのオートコンプリートコンポーネントの近くにラベル要素があるため、これを利用します。
components / autocomplete.vue
<script> export default { ... props { ... ariaLabelledBy: { type: String, required: true, }, }; }; </script> <template> ... <input type="text" v-model="search" @input="onChange" :aria-labelledby="ariaLabelledBy" /> ... </template>
誰もがそれを追加することを決して忘れないようにするために、それを必須の属性にしました。 アプリケーションにコンポーネントを囲むlabel要素がない場合は、代わりにaria-label
属性を使用する方が賢明かもしれません。
ラベルにid
を追加し、それを小道具として提供する必要があります。
app.vue
<template> <div id="app"> <div> <label id="fruitLabel">Choose a fruit:</label> <autocomplete :items="[ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']" aria-labelled-by="fruitlabel" /> </div> </div> </template>
そして今、支援技術は、入力テキストの意図が果物を選択することであることを私たちに伝えることができます:
ARIA属性
ラベルは使いやすさを大幅に向上させることができますが、それだけでは不十分であり、ユーザーはそれがオートコンプリート要素であることを認識していません。 そのためには、他のARIA属性を使用する必要があります。
role
属性がどのように機能するかを理解することから始めましょう。
ロールは、要素の要素タイプを定義します。 こちらでは、さまざまな種類の役割をすべて確認できます。
オートコンプリートに適したのはcombobox
です。
単一行のテキストボックスと、リストボックスやグリッドなどの別の要素を含む複合ウィジェット。動的にポップアップ表示され、ユーザーがテキストボックスの値を設定するのに役立ちます。
コンポーネントにテキストを入力すると、目的の値の結果のリストが表示されるため、textbox要素にaria-autocomplete属性も設定する必要があります。
aria-autocomplete
属性では、3つの異なる値を使用できます。値の完了がテキスト入力内で発生することを定義するinline
値と、list
値を意味します。値は、テキスト入力に隣接してポップアップする別の要素またはboth
値に表示されます。これは、値のリストが表示され、表示されると、リスト内の1つの値が自動的に選択されて表示されることを意味します。テキスト入力内に表示されます。
オプションのリストは別の要素にあるため、list
値を使用する必要があります。
この属性だけでは、値のリストがドキュメント内のどこにあるかを魔法のように認識しないため、aria-controls属性を使用して指定する必要があります。
また、オートコンプリートが aria-haspopup 属性で識別され、結果のリストが表示されるたびにコンテナーにaria-expanded属性が設定されていることを確認する必要があります。
最後になりましたが、role
属性をsearchbox
値でinput
に追加し、[でul
要素に追加する必要があります。 X141X]およびrole
値を持つ各li
に。
これらの属性により、支援技術ソフトウェアは、提案された値のリストを表示するコンボボックスをユーザーに提示していることを理解できるようになりました。
components / autocomplete.vue
<template> <div class="autocomplete" role="combobox" aria-haspopup="listbox" aria-owns="autocomplete-results" :aria-expanded="isOpen" > <input type="text" @input="onChange" v-model="search" @keyup.down="onArrowDown" @keyup.up="onArrowUp" @keyup.enter="onEnter" aria-multiline="false" role="searchbox" aria-autocomplete="list" aria-controls="autocomplete-results" aria-activedescendant="" :aria-labelledby="ariaLabelledBy" /> <ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results" role="listbox" > <li class="loading" v-if="isLoading"> Loading results... </li> <li v-else v-for="(result, i) in results" :key="i" @click="setResult(result)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }" role="option" > {{ result }} </li> </ul> </div> </template>
矢印のサポート
オートコンプリートコンポーネントにキーボードサポートを追加したことを覚えていますか? ARIA属性でも管理する必要があります。
矢印キーを使用するときにどのオプションが選択されているかを支援技術が認識できるようにするには、次の2つの属性を設定する必要があります。
aria-activedescendant は入力フィールドで設定する必要があり、キーボードフォーカスがあると視覚的に識別されるオプションのIDを保持します。
また、 aria-selected は、選択済みとして視覚的に強調表示されたオプションのli
属性で設定する必要があります。
コンポーネントで更新する必要がある重要なことの1つはリスナーです。支援技術がアクティブなオプションを正しく識別するために、keyup
ではなくkeydown
イベントをリッスンする必要があります。イベント。
完全なソースコードは、次のスニペットまたはこのcodepenで確認できます。
components / autocomplete.vue
<script> export default { name: 'autocomplete', props: { items: { type: Array, required: false, default: () => [], }, isAsync: { type: Boolean, required: false, default: false, }, ariaLabelledBy: { type: String, required: true } }, data() { return { isOpen: false, results: [], search: '', isLoading: false, arrowCounter: 0, activedescendant: '' }; }, methods: { onChange() { this.$emit('input', this.search); if (this.isAsync) { this.isLoading = true; } else { this.filterResults(); } }, filterResults() { this.results = this.items.filter((item) => { return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1; }); }, setResult(result) { this.search = result; this.isOpen = false; }, onArrowDown(evt) { if (this.isOpen) { if (this.arrowCounter < this.results.length) { this.arrowCounter = this.arrowCounter + 1; this.setActiveDescendent(); } } }, onArrowUp() { if (this.isOpen) { if (this.arrowCounter > 0) { this.arrowCounter = this.arrowCounter -1; this.setActiveDescendent(); } } }, onEnter() { this.search = this.results[this.arrowCounter]; this.arrowCounter = -1; }, handleClickOutside(evt) { if (!this.$el.contains(evt.target)) { this.isOpen = false; this.arrowCounter = -1; } }, setActiveDescendant() { this.activedescendant = this.getId(this.arrowCounter); }, getId(index) { return `result-item-${index}`; }, isSelected(i) { return i === this.arrowCounter; }, }, watch: { items: function (val, oldValue) { // actually compare them if (val.length !== oldValue.length) { this.results = val; this.isLoading = false; } }, }, mounted() { document.addEventListener('click', this.handleClickOutside) }, destroyed() { document.removeEventListener('click', this.handleClickOutside) } }; </script> </script> <template> <div class="autocomplete" role="combobox" aria-haspopup="listbox" aria-owns="autocomplete-results" :aria-expanded="isOpen" > <input type="text" @input="onChange" @focus="onFocus" v-model="search" @keydown.down="onArrowDown" @keydown.up="onArrowUp" @keydown.enter="onEnter" role="searchbox" aria-autocomplete="list" aria-controls="autocomplete-results" :aria-labelledby="ariaLabelledBy" :aria-activedescendant="activedescendant" /> <ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results" role="listbox" > <li class="loading" v-if="isLoading" > Loading results... </li> <li v-else v-for="(result, i) in results" :key="i" @click="setResult(result)" class="autocomplete-result" :class="{ 'is-active': isSelected(i) }" role="option" :id="getId(i)" :aria-selected="isSelected(i)" > {{ result }} </li> </ul> </div> </template>
オートコンプリートアクセシビリティのチートシート
ここでは、オートコンプリートにアクセスできるようにするために必要なすべてのARIA属性を含む虎の巻を見つけることができます。
エレメント | 属性 | 価値 | 使用法 |
---|---|---|---|
div
|
role
|
combobox
|
要素をコンボボックスとして識別します |
div
|
aria-haspopup
|
listbox
|
要素が提案された値でlisboxをポップアップすることを識別します |
div
|
aria-owns
|
IDREF
|
提案されたリスト値で要素を識別します |
div
|
aria-expanded
|
true
|
提案された値のリストが現在展開されているか折りたたまれているかを示します |
input
|
role
|
searchbox
|
要素を検索ボックスとして識別します |
input
|
aria-labelledby
|
IDREF
|
コンボボックス要素のラベルを提供します |
input
|
aria-autocomplete
|
list
|
ユーザーが入力を提供しているときに、提案された値のリストを含む要素が表示されることを示します |
input
|
aria-controls
|
IDREF
|
|
input
|
aria-activedescendant
|
IDREF
|
結果のリスト内のオプションがキーボードフォーカスを持っていると視覚的に識別されると、そのオプションを参照します |
ul
|
role
|
listbox
|
要素をリストボックスとして識別します |
li
|
role
|
option
|
要素をリストボックスオプションとして識別します |
li
|
aria-selected
|
true
|
選択されたものとして視覚的に識別されているものとして要素を識別します |
IDREF :要素のID属性への参照