私たちが構築したこのオートコンプリートコンポーネントを覚えていますか? ほとんどのユーザーはそれを使用できますが、Webを閲覧するために支援技術を必要とする障害を持つ人々はそれを使用できません。 これは、これらのテクノロジーが、コンポーネントが通常の入力以上のものであることを理解するのに十分なセマンティックを作成しなかったためです。
この記事では、ARIA属性を使用して、オートコンプリートを完全にアクセス可能なものにする方法を学習しました。
アクセス可能なリッチインターネットアプリケーション(ARIA)
支援技術を使用してWebを閲覧しようとしたことがありますか? ほとんどの運用システムには統合ソリューションが付属しています。MacOSでは、VoiceOverを押すと開くことができます cmd + F5
Windowsでは、ナレーターを押すと起動できます Windows logo key + Ctrl + Enter
.
上記のいずれかをこのオートコンプリートコンポーネントで使用すると、オートコンプリートがテキストフィールドであり、オプションのリストについて通知されないことがわかります。
ARIA属性を使用してこれを変更できます。 ARIA仕様は、支援技術ソフトウェアがコンテンツのセマンティクスを理解できるようにする一連の属性を提供することにより、障害を持つ人々がWebコンテンツを使用できるようにする方法を定義しています。
ラベルは重要です
シンプルなラベルがどれだけ使いやすさを向上させることができるかに驚かれることでしょう。
コンポーネントをアプリケーションにすばやくセットアップし、VoiceOverを使用して操作してみましょう。
<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
代わりに、ほとんどのオートコンプリートコンポーネントの近くにラベル要素があるため、これを利用します。
<script>
export default {
...
props {
...
ariaLabelledBy: {
type: String,
required: true,
},
};
};
</script>
<template>
...
<input
type="text"
v-model="search"
@input="onChange"
:aria-labelledby="ariaLabelledBy"
/>
...
</template>
誰もがそれを追加することを決して忘れないようにするために、それを必須の属性にしました。 アプリケーションにコンポーネントを囲むラベル要素がない場合は、を使用する方が賢明かもしれません。 aria-label
代わりに属性。
追加する必要があります id
私たちのラベルにそしてそれを小道具として提供するために:
<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
1:
単一行のテキストボックスと、リストボックスやグリッドなどの別の要素を含む複合ウィジェット。動的にポップアップ表示され、ユーザーがテキストボックスの値を設定するのに役立ちます。
コンポーネントにテキストを入力すると、目的の値の結果のリストが表示されるため、textbox要素にaria-autocomplete属性も設定する必要があります。
The aria-autocomplete
属性は3つの異なる値を許可します。 inline
値の完了が内部テキスト入力で発生することを定義する値と list
値は、テキスト入力に隣接してポップアップする別の要素に値が存在することを意味します。 both
valueは、値のリストが表示され、表示されると、リスト内の1つの値が自動的に選択され、テキスト入力内に表示されることを意味します。
オプションのリストは別の要素にあるため、を使用する必要があります list
価値。
この属性だけでは、値のリストがドキュメント内のどこにあるかを魔法のように認識しないため、aria-controls属性を使用して指定する必要があります。
また、オートコンプリートが aria-haspopup 属性で識別され、結果のリストが表示されるたびにコンテナーにaria-expanded属性が設定されていることを確認する必要があります。
最後になりましたが、追加する必要があります role
私たちの属性 input
とともに searchbox
値、に ul
要素と listbox
そしてそれぞれに li
と role
価値。
これらの属性により、支援技術ソフトウェアは、提案された値のリストを表示するコンボボックスをユーザーに提示していることを理解できるようになりました。
<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つはリスナーです。支援技術がアクティブなオプションを正しく識別するために、リッスンする必要があります。 keydown
の代わりにイベント keyup
イベント。
完全なソースコードは、次のスニペットまたはこのcodepenで確認できます。
<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属性への参照