WAI-ARIAを学ぶときに整理しておきたいこと
結論
- ロールについて知る
- HTMLの暗黙のロールを知る
- ロールを知った上でロールに対して使用できるプロパティ/ステートを使う
- (おまけ) markuplintを使おう
aria属性を使う前に
まず、いきなりaria-label
やaria-selected
とかに手を出さない。
aria-selected
とかを発見してしまうと「option
要素以外にもselected
みたいな意味を付加できるんだ!すげえ!使ってみよう!」みたいな気持ちが沸き上がってしまう。わかる。とってもよくわかるよ。当時ぼくもそうだったから。
ただ、そこはぐっと我慢してほしい。 なぜかと言うと、aria属性は、使っていいときと悪いときがある。きちんとWAI-ARIAという仕様と、ARIA in HTMLやCore Accessibility API Mapping (Core-AAM)という仕様で決められていっている[1]ので、それに準拠することがアクセシブルなコンポーネント作るうえで重要だ。ARIAは、悪い使い方や組み合わせでは目的と逆にアクセシビリティを低くしてしまう。気をつけないといけない。まずは落ち着いて、仕様を理解しよう。
とは言っても仕様を全部完全に理解するのは難しい…。そこで、今回は、どういう順番で確認していくと、勘違いせずに使えるようになるのか、ぼくの経験から紹介したい。
ロールがあって、そしてプロパティとステートがある
aria属性は「プロパティ」もしくは「ステート」と呼ばれるものを設定する属性だ。たとえばaria-label
はプロパティで、aria-selected
はステートだ。雑に分けると、静的な情報がプロパティで、動的な情報がステートになる。
そしてここからが重要で、そのプロパティとステートは、ロールに対してのプロパティとステートということだ。
たとえば、
<button aria-label="ヘルプ">?</button>
コードとしてはbutton要素にaria-label
属性を設定しているが、「button要素のaria-label
という属性」を設定しているというよりも、「buttonロールのaria-label
プロパティ」を設定しているということを意識する必要がある。
ロール?突然だな?と思うかもしれないが、ロールが超重要。なぜかというとプロパティ/ステートは、ロールに基づいて設定できるもの・できないものが決まっている。button
というロールにはaria-label
が指定できるので、例のコードが成り立つのだ。
だから、プロパティやステートを設定する前に、ロールというものを知る必要がある。
ロールについて
じゃあロールとは何なのか。ロールを理解するには、次について知っておくとよい。
- 暗黙のロール
- role属性
- 抽象ロール
暗黙のロール
WAI-ARIAは、HTML5よりも歴史が古く、もともとHTML4の時代から存在していた。その名の通りアクセシブルなリッチインターネットアプリケーション(つまり今でいうWebアプリ)を作るための仕様だったので、その当時のHTMLにない語彙が規定された[2]。そしてHTML5になったときにはWAI-ARIAの一部がHTMLに輸入されて、要素や属性が大量に増えたという経緯がある。
そして、HTMLの各要素には暗黙的にロールが規定されることなった。button要素ならbutton
ロール、a要素ならlink
ロールといった具合に、すべての要素に何かしらのWAI-ARIAで規定されたロールが対応する。これを暗黙のロール[3]という。
HTML要素 | 対応する暗黙のロール |
---|---|
a |
link |
button |
button |
img |
image |
header |
banner [4]
|
footer |
contentinfo [5]
|
nav |
navigation |
ul |
list |
li |
listitem |
div |
generic |
span |
generic |
さてここで思い出して欲しいのは、プロパティ/ステートはロールによって設定できる・できないが決まっているということ。ということはつまり、aria属性は要素によって設定できる・できないが決まっているということだ。最初に述べたことにもどるけれど、こういう規定があるのでaria属性の設定は闇雲にしてはいけない。
role属性
未だにHTMLに輸入されてない語彙もWAI-ARIAには存在する。たとえばタブUIを構成するための tab
tablist
tabpanel
がそうだ。HTMLに <tab>
<tablist>
<tabpanel>
ができてくれると嬉しいけど、現段階では存在しない。そんなときに要素にロールを与えるのがrole属性だ。
<ul role="tablist">
<li role="tab">tab1</li>
<li role="tab">tab2</li>
<li role="tab">tab3</li>
</ul>
<div role="tabpanel">content1</div>
<div role="tabpanel">content2</div>
<div role="tabpanel">content3</div>
このようにrole属性を使えば、要素に任意のロールを与えることができる。
しかし、このrole属性を使用する場合には次のことに注意しよう。
上書きできないロールがある
<!-- 👎 ダメ -->
<img role="banner" />
<!-- 👍 許される(けど後述に注意) -->
<img role="button" />
各要素に上書きできるロールは決まっていて、 img要素の場合、button
やlink
などのロールは与えることができるがbanner
は与えることができない[6]。
role属性自体も無闇に指定できるものではない、と覚えておこう。
既に要素に対応するものがあるロールは使用するべきではない、既存の要素をつかう
そして、img要素にbutton
ロールを与えられるとしても、button
ロールは既にbutton要素が暗黙のロールをもっているので、そちらを利用するべきだ[7]。
<!-- 👎 良くない -->
<img role="button" />
<!-- 👍 こっちが良い -->
<button><img /></button>
暗黙のロールを明示しない
暗黙的にもっているロールを敢えてrole属性で指定することは冗長なので避ける[8]。
<!-- 👎 良くない -->
<nav role="navigation">...</nav>
<!-- 👍 こっちが良い -->
<nav>...</nav>
抽象ロール
ロールには、スーパークラスやサブクラスといったモデル体系のヒエラルキーが存在する。スーパークラスの一部は抽象ロールとされていて、使用できない。たとえばinput
というロールが存在するが、このロールはcheckbox
ロールやradio
ロールの元となるスーパークラスロールで、role属性などで使うことはできない。
とりあえず使用できないロールもあると覚えておいて、仕様を読むときに抽象ロールかどうか確認できればOKだ。
ロールに規定されること
ロールには、それに基づいて設定できるプロパティ/ステートが決まっているように、他にも知っておくべき規定がある。
- アクセシブルな名前が必須かどうか
- アクセシブルな名前がコンテンツから得られるかどうか[9]
- アクセシブルな名前の明示指定が禁止かどうか[10]
- 必須プロパティ/ステート
- 禁止プロパティ/ステート
- 非推奨(廃止予定)プロパティ/ステート
- プロパティ/ステートのデフォルト値
- 必須の構造
- 子のプレゼンテーション[11]
アクセシブルな名前
アクセシブルな名前とは、そのロールが持つ名前で、コンテンツ(要素が内包する内容やテキスト)やaria-label
で設定されたもので、たとえば次のようになる。
<!-- 「押すなよ、絶対押すなよ」というアクセシブルな名前を持つ -->
<button>押すなよ、絶対押すなよ</button>
<!-- 「ヘルプ」というアクセシブルな名前を持つ -->
<button aria-label="ヘルプ">?</button>
aria-label
で設定できるかどうか、コンテンツから設定できるかどうかはロールによって異なる。先の例のbutton
ロールでは「コンテンツ由来[12]」であり「著者由来[13]」であるので、どちらでも設定可能だ。
「コンテンツ由来」でない、つまりコンテンツはアクセシブルな名前にならないロールもある。
<!-- アクセシブルな名前は持たない -->
<nav>...ナビゲーション...</nav>
<!-- 「メインメニュー」というアクセシブルな名前を持つ -->
<nav aria-label="メインメニュー">...ナビゲーション...</nav>
<!-- 「メインメニュー」というアクセシブルな名前を持つ -->
<nav aria-labelledby="nav-name">
<h2 id="nav-name">メインメニュー</h2>
...ナビゲーション...
</nav>
navigation
ロールは「著者由来」だけだからだ。
そして重要なのが、アクセシブルな名前を持てないロールもある。持つことを禁止されている[14]ので、同時にaria-label
とaria-labelledby
が設定禁止になっている。
<!-- 👎 genericロールはアクセシブルな名前を持てないのでNG -->
<div aria-label="無効なラベル">...</div>
<!-- 👎 strongロールはアクセシブルな名前を持てないのでNG -->
<strong aria-label="無効なラベル">...</strong>
ちなみに、「コンテンツ由来」だけのロールは存在しない。
コンテンツやaria-label
からアクセシブルな名前が決定される優先順位はAccessible Name and Description Computation(通称「Accname」)によって計算される。実際にはHTMLのその他の属性やCSSの表示状態よっても決定されるので、気になる場合は仕様を読んでみるといい。
プロパティ/ステートの必須・禁止・非推奨
それぞれしっかりロールごとに決まっているので、仕様を確認すると良い。
プロパティ/ステートのデフォルト値
これもロールごとにそれぞれデフォルト値の決まっているプロパティ・ステートがある。
必須の構造
li要素がul要素の子要素としか存在できなように、ロールもまた構造ルールをもつものがある。list
ロールとlistitem
ロール、menu
ロールとmenuitem
ロール、tablist
ロールとtab
ロールなど。組み合わせもさまざまなので、注意深く確認したほうがいいだろう。
その中でpresentation
ロールは特殊な定義となっている。
<ul role="tree">
<li role="presentation">
<a role="treeitem">ツリーアイテム</a>
</li>
</ul>
tree
ロールとtreeitem
ロールの構造は互いに必須だが、presentation
ロールを挟むことができる[15]。
プロパティとステート
やあ、お待たせ。ここまでくれば、やっとプロパティとステートの説明ができる。ロールが理解できたのなら、あとはその目的のロールに合わせてプロパティを使っていくだけだ。
論理属性ではない
プロパティ/ステートを設定するときの注意点として、aria属性は論理属性ではないということだ。論理値をとるステートだとしても、文字列として設定する必要がある。
<!-- 👎 無効 -->
<button aria-pressed>ボタン</button>
<!-- 👍 有効 -->
<button aria-pressed="true">ボタン</button>
<button aria-pressed="false">ボタン</button>
また、未設定の場合は未設定の値undefined
となるプロパティ/ステートもあるので、よく仕様を確認すること。ちなみにaria-pressed
は、true
/false
/mixed
/undefined
を受け付ける。
HTMLとは異なる文化で定義された仕様ということを意識しておくと、よいかもしれない。
暗黙のプロパティ/ステート
ここまでの関係性がわかってくると、次のコードの問題点を予想できるんじゃないだろうか。
<input type="checkbox" aria-checked="false" />
このチェックボックスは、ユーザーがチェックをしてもARIAのステートは false
のままだ。ブラウザに描画される見た目や、DOMプロパティ[16]としては「チェックされている」状態にきちんとなっていても、ARIAステートは「チェックされていない」ことになる。スクリーンリーダーほか支援技術だけクリティカルな問題となる一番避けたい重大なバグを作ってしまう。JavaScriptを使ってchangeイベントなどで適切にステートを切り替えていれば問題にはならないが、だったらそもそもaria-checked
を指定しないほうがいい。わざわざスクリプトで制御する必要もなくなる。
仕様でもこれは言及されていて[17]、次のステートは特に注意するとよい。
ステート | 注意すべきロール |
---|---|
aria-checked |
checkbox radio
|
aria-haspopup |
combobox |
aria-selected |
option |
aria-multiselectable |
combobox listbox
|
aria-valuemax |
slider meter progressbar
|
aria-valuemin |
slider meter
|
意味の重複定義
また、意味の同じプロパティ/ステートと属性を同時に設定することをARIA in HTMLでは「すべきでない」とされている[18]。暗黙のロールを明示しないと同じ扱いだと思えばよい[19]。殆どの場合、HTMLの本来持っている属性の振る舞いをaria属性は保証しないので、独自に実装しないといけない。そんなことならaria属性はやっぱり指定するべきではない。もちろん、矛盾する定義は禁止だ。
<!-- 👎 重複するべきではない -->
<input type="text" readonly aria-readonly="true" />
<!-- 👍 片方なら良い -->
<input type="text" readonly />
<!-- 🤔 禁止はされていないけど、結局readonly属性と同じ振る舞いを実装しないといけない -->
<input type="text" aria-readonly="false" />
<!-- 🚫 ダメ、ゼッタイ -->
<input type="text" readonly aria-readonly="false" />
まとめ
- aria属性が使いたくなったら…
- その要素の暗黙のロールを調べる。
- ロールが適切であればそのまま。他に最適なロールがあればそれを暗黙的にもつ要素に変える。要素がなければrole属性でロールを上書きする(上書きできない場合があるので注意)。
- ロールの必須プロパティ/ステートを設定する。
- 他に必要なプロパティ/ステートを設定する。
- 必要な構造関係があれば、暗黙ロールをもつ要素や任意にロールをもたせた要素を追加する。
- プロパティ/ステートの値が正しいか確認する。
(おまけ) markuplintを利用する
最後に、著作が開発しているmarkuplintを紹介させてほしい。
markuplintはWAI-ARIAをチェックするルール(wai-aria
日本語版)を持っている。これまで紹介した規格のほとんどを機械的に見つけることができる。特にVisual Studio Codeの拡張をインストールすれば、コーディング中に問題を検知できる。何が間違いなのかフィードバックを受けながら開発ができるので、学習面でも利用価値があると思っている。HTMLに限らずPugやEJS、そしてReactやVueやSvelteにも対応しているので、是非使ってみて欲しい。
-
正式勧告されたわけではないので継続的に新しい機能が追加されていっている段階 ↩︎
-
HTMLに限らず、SVGやXMLなど、他のマークアップ言語にも適応できる前提で今も昔も規定されています。 ↩︎
-
「暗黙のセマンティクス」という言い方をされることもある。 ↩︎
-
これはWAI-ARIAではなく、ARIA in HTMLで規定される。 https://www.w3.org/TR/html-aria/#el-img ↩︎
-
じゃあ、なんで上書きできるようになってるんだろうね。禁止しちゃえばいいのにね。 ↩︎
-
スクリーンリーダなどの支援技術で、古いものはHTMLとARIAのマッピングができていないものもあるので、それに対応するために致し方なく指定する場合はある。レガシーなブラウザへのサポートと同様に方針を立てて対応すべし。 ↩︎
-
得られる場合、
aria-label
とaria-labelledby
が無くとも要素の内容(もしくはalt属性やtitle属性)からアクセシブルな内容が決定する。 ↩︎ -
禁止される場合、
aria-label
とaria-labelledby
の設定が禁止される。アクセシブルな名前はもたない。 ↩︎ -
たとえば
img
ロールやcheckbox
ロールなど、HTMLであれば空要素に相当してコンテンツが存在し得ないロールの場合、このロールとなった子要素はプレゼンテーション、つまりアクセシビリティツリーから削除されることになります。 ↩︎ -
仕様では"Name From: content"と定義される。innerTextが名前となる。 ↩︎
-
仕様では"Name From: author"と定義される。「著者」という少々わかりにくい表現であるが、
aria-label
もしくはaria-labelledby
で明示的に設定されたものが名前となる。 ↩︎ -
仕様では「Name From: prohibited」と定義される。 ↩︎
-
このトランスペアレントに振る舞えることに言及した部分を仕様に見つけることができなかった。https://www.w3.org/TR/wai-aria-1.2/#example-12 ではセマンティックスの上書きに言及されているけど…。 ↩︎
-
checked
プロパティで参照できる。 https://developer.mozilla.org/ja/docs/Web/API/HTMLInputElement#Properties_checkbox_radio ↩︎ -
ARIA in HTMLで"Authors SHOULD NOT use the aria-checked attribute on input type=checkbox elements."と言及されている。 https://www.w3.org/TR/html-aria/#el-input-checkbox ↩︎
-
Document conformance requirements for use of ARIA attributes with HTML attributes ↩︎
-
ロールと同様、非対応な支援技術のために仕方なく設定する場合もあるだろう。その場合、属性値が変化するならばプロパティ/ステートも同期的に変化するように実装しなければならない。 ↩︎
Discussion
img要素の暗黙のロールは
image
ではなくて、img
☞ https://www.w3.org/TR/html-aria/#el-imgpresentation
☞ https://www.w3.org/TR/html-aria/#el-img-empty-altimg
☞ https://www.w3.org/TR/html-aria/#el-img-no-altのようです。
WAI-ARIA を導入ときは覚悟が必要ですね。
WAI-ARIA は複雑で大きすぎる力を持つものなので導入以後は徹底的にアクセシビリティ検査やテストをするということを意味する。