WAI-ARIA中級編 ロールの決まり方
前回の「WAI-ARIAを学ぶときに整理しておきたいこと」(以下、「前回の記事」と略)やYouTubeで生配信した「WAI-ARIA勉強会」で
- 要素には暗黙のロールをもつ
-
role
属性を使うことでロールを上書きできる - 要素によって上書きできないロールがある
と説明した。
しかし、上書きできないロールをrole
属性で指定してしまったときに最終的にロールが何になるかの説明をしていなかったし、要素の条件次第によっては上書き可能なはずのロールが無効化されたり、ロールが変化したりすることに触れていなかった。今回はそこが仕様でどうなっているのか深堀りしてみたいと思う。
ロールの決定に影響するもの
まず、簡単にロールがどういったものから決定されるのかをざっくりと最初にまとめる。
- 要素の種類(HTMLとしてのセマンティック)
- 属性
- 構造(先祖・子孫関係)
これをまず念頭に置いて、仕様を読みすすめると理解がしやすいと思う。ひとまずは複雑なことを予め覚悟して読み進めていきたい。
暗黙のロールはどう決まるのか
まずは暗黙のロールが定義されているARIA in HTMLを確認していこう。
属性による暗黙のロール
仕様を見てみると早速、属性が関係していることがわかるだろう。「a with href」「a without href」という記述があるように、href
属性をもつa
要素と、それを持たないa
要素で判定される暗黙のロールが異なる。
<a href="path/to"><!-- ロールは link --></a>
<a><!-- ロールは generic --></a>
img
要素はさらにややこしい。
-
alt
なし -
alt
が空 -
alt
にテキストが入っている(つまり空ではない)
の3つ条件があり、「alt
なし」と「alt
にテキストが入っている(つまり空ではない)」はロールがimg
になり、alt
が空はロールがpresentation
になる。
<img src="path/to" /><!-- ロールは img -->
<img src="path/to" alt="" /><!-- ロールは presentation -->
<img src="path/to" alt="何かしらのテキスト" /><!-- ロールは img -->
属性そのものの有無だけでなく、値によってもロールは変わるということだ。
値によってロールが変わることについては、input
要素はわかりやすい。type
属性によってロールが変化する。チェックボックスであればcheckbox
ロールになるし、ラジオボタンであればradio
ロールになる。
ちなみに、
input type=text or with a missing or invalid type, with no list attribute
という条件の with a missing or invalid type というのは「type
属性がない」もしくは「type
属性に無効なデタラメの値がある」の状態をことを指していて、HTMLにも定義されているとおり、属性がなかったり属性値が無効だった場合はtype=text
と同じ扱いになるため、type=text
と同じ条件となっている。
構造による暗黙のロール
さらに見ていくと、先祖要素によってロールが変わるものがあることもわかる。header
要素の暗黙のロールは次のように定義されている。
If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=banner
Otherwise, role=generic
否定文から始まっているので非常にややこしいんだけど、つまり言い換えると、
- 基本的には
banner
ロール - ただし祖先に
article
、aside
、main
、nav
、section
要素、またはrole
属性にarticle
、complementary
、main
、navigation
、region
をもつ要素があればgeneric
ロール
ということだ。
<body>
<header><!-- ロールは banner --></header>
</body>
<body>
<article>
<header><!-- ロールは generic --></header>
</article>
<div role="article">
<header><!-- ロールは generic --></header>
</div>
</body>
td
要素は3パターンある。
role=cell if the ancestor table element is exposed as a role=table
role=gridcell if the ancestor table element is exposed as a role=grid or treegrid
No corresponding role if the ancestor table element is not exposed as a role=table, grid or treegrid
- 先祖の
table
要素がtable
ロール(暗黙のロールなのでrole
属性なしの場合も)だったらcell
ロール - 先祖の
table
要素がgrid
ロールもしくはtreegrid
ロールだったらgridcell
ロール - 先祖の
table
要素が上記以外だったらロールなし
<table>
<tr>
<td><!-- ロールは cell --></td>
</tr>
</table>
<table role="grid">
<tr>
<td><!-- ロールは gridcell --></td>
</tr>
</table>
<table role="treegrid">
<tr>
<td><!-- ロールは gridcell --></td>
</tr>
</table>
<table role="presentation">
<tr>
<td><!-- ロールは なし --></td>
</tr>
</table>
th
要素も同様の3パターンなのだが、結果が role=columnheader, rowheader or gridcell と曖昧になってるところが現仕様として困りどころだ。
おそらくは「thead
要素を親にもつ」とか「scope
属性による」だったりの条件が明記されているのが望ましいのだけどARIA in HTMLには言及がない。WAI-ARIA 1.2のcolumnheader
の説明にはBase Conceptとして、 <th[scope="col"]> in [HTML] と記載があるので、まあそういうことなんだろうとは思うんだけど、ちゃんと厳密に記載して両仕様で足並みを揃えて欲しいところだ。
アクセシブルな名前の有無による暗黙ロール
section
要素は、アクセシブルな名前を持てばregion
ロールになり、持たなければロールはない。
<section aria-label="なにかしらのテキスト"><!-- ロールは region --></section>
<section aria-labelledby="heading-id">
<!-- ロールは region -->
<h2 id="heading-id">なにかしらのテキスト</h2>
</section>
<section><!-- ロールは なし --></section>
さて、ここまでは要素がもつ暗黙のロールであくまでも基本。本番はここからだ。
role
属性による明示的なロールの決まり方
基本的にはrole
属性に明示的にロールを指定すれば、暗黙のロールを上書きすることになるが、以下の条件では指定が無視される。
仕様にないロールは無視される
これは言わずもがな。当然、仕様にないロールは明示しても意味がない。
<div role="my-role"><!-- ロールは generic --></div>
抽象ロールは無視される
これは「前回の記事」でも説明したとおり。ロールには抽象ロールに属するものがあるので注意が必要。
<div role="section"><!-- ロールは generic --></div>
上書きできないロールは無視される
これも「前回の記事」で言及しているが、より詳しく説明すると、これは「ARIA in HTML」の ARIA roles, states and properties which MAY be used の列を参照することになる。
ここでも暗黙のロールと同様、要素の条件によって使用できるロールが決まっている。
a with href ではbutton
をはじめとしたいくつかのロールしか認められていないが、a without href では全てのロールが認められている。
<a href="path/to" role="button"><!-- ロールは button --></a>
<a href="path/to" role="alert"><!-- ロールは link (alertは認められない) --></a>
<a role="alert"><!-- ロールは alert --></a>
アクセシブルな名前をもたない場合に無視されるロールがある
Handling Author Errorsという解説にWAI-ARIA 1.3[1]で追記された新しい仕様がある。
Certain landmark roles require names from authors. In situations where an author has not specified names for these landmarks, it is considered an authoring error. The user agent MUST treat such elements as if no role had been provided. If a valid fallback role had been specified, or if the element had an implicit ARIA role, then user agents would continue to expose that role, instead. Instances of such roles are as follows:
- form
- region
これは、form
とregion
ロールは明示してもアクセシブルな名前を持たなければ無視されますよ、という仕様だ。
<div role="form"><!-- ロールは generic --></div>
<div role="form" aria-label="何かしらのテキスト"><!-- ロールは form --></div>
暗黙のform
ロールをもっているfrom
要素の場合どうなのかというと、現行のARIA in HTMLのform
要素には A form is not exposed as a landmark region unless it has been provided an accessible name. という注釈があり、アクセシブルな名前がなければ実質ロールが機能しないことを示している。(ロールが機能しないことと、ロールが無視されることの差はなんなのか曖昧なところは、もっと厳密に決定して欲しいところ)
どのみち、ロールには「アクセシブルな名前が必須」となる場合があり、form
とregion
はどちらもそうなので、仕様に順当に従えば気にしなくてよい細かい仕様かもしれない。
必須の構造を満たしていない場合にロールが消失することがある
ロールにはRequired Context Roleの条件をもっているものがあり、たとえばlistitem
ロールの場合、list
かdirectory
ロールを親にもっていないといけない。
For example, an element with role listitem is only meaningful when contained inside (or owned by) an element with role list.
only meaningful という言葉を使っていて、ロールがgeneric
になったりロールなしになるとは言及されていないので解釈はブラウザ次第になるが、いくつかのブラウザではロールが消失したり、アクセシビリティツリーに要素そのものが公開されないケース[2]があるので注意が必要だ。
<ul>
<li><!-- ロールは listitem --></li>
</ul>
<ul role="generic">
<li>
<!-- ロールが消失したり、アクセシビリティツリーに公開されない場合がある -->
</li>
</ul>
<ul role="tablist">
<li>
<!-- ロールが消失したり、アクセシビリティツリーに公開されない場合がある -->
</li>
</ul>
<div>
<li>
<!-- ロールが消失したり、アクセシビリティツリーに公開されない場合がある -->
</li>
</div>
ちなみに、table
要素にrole="presentation"
を指定することで子孫要素の意味を打ち消すテクニックを使うことがあるけど、その場合この仕様が作用しているとも言えるし、ARIA in HTMLの暗黙のロールの条件「先祖のtable
要素がtable
、grid
、treegrid
以外のロールだったらロールなし」という仕様が作用しているとも言える。このあたり、各仕様が絶妙に辻褄をあわせているところは仕様を読んでいて面白い。
presentation
指定しても無視される少し特殊なロールとしてpresentation
ロールがあるが、このロール固有で無視されるケースがあることがWAI-ARIA 1.2のPresentational Roles Conflict Resolutionで決められている。
presentation
ロールはアクセシビリティツリーに要素を公開しないロールで、つまりrole="presentation"
と記述すれば、支援技術からみたら何もない状態にすることができる。
presentation
ロールの使い所としては
<ul role="tablist">
<li role="presentation"><button role="tab">Apple</button></li>
<li role="presentation"><button role="tab">Orange</button></li>
<li role="presentation"><button role="tab">Banana</button></li>
</ul>
のように、tablist
とtab
の親子関係の間にある要素を透明にするような場合に効果的に利用できる。
presentation
は無視される
フォーカス可能であればIf an element is focusable, or otherwise interactive, user agents MUST ignore the presentation role and expose the element with its implicit role, in order to ensure that the element is operable.
ごくごくシンプル。フォーカスできたりインタラクティブであれば、明示的にpresentation
を指定しても無視され、暗黙のロールにフォールバックされる。
理由は「ユーザーが操作できるのに、公開されてないのはおかしいでしょ?」ということ。
<a href="path/to" role="presentation"><!-- ロールは link --></a>
<button role="presentation"><!-- ロールは button --></button>
<input type="text" role="presentation" /><!-- ロールは textbox -->
<div tabindex="0" role="presentation"><!-- ロールは generic --></div>
ただしWAI-ARIAはHTMLのみに言及できる仕様ではないため、HTMLの各要素のどれがフォーカス可能またはインタラクティブ要素かどうかには明記していない(interactive = コンテンツ・モデルがインタラクティブコンテンツの要素とは明言していない)。ブラウザがどう解釈してどう実装されるかはブラウザ次第になる。この曖昧さに関してはIssueが作られていて、まだクローズされていない。
また、サンプルコードに記載はしたものの、ARIA in HTMLではhref
をもつa
要素も、button
要素も、type="text"
をもつinput
要素も、そもそもpresentation
で上書きできないことになっているので、その仕様に則っても無視されて暗黙のロールにフォールバックされるのは同じである。ここもARIA in HTMLとWAI-ARIAで矛盾が発生しないようになっている。
presentation
が無視される
継承された前提の知識としてWAI-ARIAのRequired Owned Elementという仕様がある。これは、たとえば「list
ロールの子はlistitem
でなければならない」といった構造上のルールだ。
<ul>
<!-- ul のロールは list なので -->
<li>
<!-- 子となる li のロールは listitem でなければならない。ここでは違反していない。 -->
</li>
</ul>
そして、このRequired Owned Elementにおいてpresentation
ロールは継承される性質を持つ。
<ul role="presentation">
<!--
ul のロールは list で元々 Required Owned Element をもつ
この ul 自身のロールは presentation
-->
<li>
<!--
親が presentation となった Required Owned Element なので
ロールは presentation
-->
</li>
</ul>
そして、そのpresentation
が無視される条件がある。
If a required owned element has an explicit non-presentational role, user agents MUST ignore an inherited presentational role and expose the element with its explicit role. If the action of exposing the explicit role causes the accessibility tree to be malformed, the expected results are undefined.
- Required Owned Elementの要素
-
presentation
を継承している - 明示的なロール(
presentation
でない)をもつ
の3つの条件が揃えば、継承されたpresentation
は無視されて、明示的なロールが適用される。ただし、これの結果、構造が不正になる場合はロールが未定義になってしまう。
う〜ん、この仕様は何度読んでも難しい。そもそもpresentation
を継承しているということはRequired Owned Elementの要素ということになるし、親がpresentation
の状態ということはRequired Context Roleから構造の不正にになるので、どう考えても未定義にしかなりえないと思うんだけど…何か重要な仕様を見逃してるのかもしれない…。※ご指摘待ってます。
presentation
は無視される
グローバルWAI-ARIAプロパティをもつとIf an element has global WAI-ARIA states or properties, user agents MUST ignore the presentation role and expose the element with its implicit role.
要素がグローバルなWAI-ARIAプロパティをもつのなら、presentation
ロールは無視されて要素は公開される。
<div role="presentation" aria-busy="false"><!-- ロールは generic --></div>
<div role="presentation" aria-live="polite"><!-- ロールは generic --></div>
However, if an element has only non-global, role-specific WAI-ARIA states or properties, the element MUST NOT be exposed unless the presentational role is inherited and an explicit non-presentational role is applied.
説明には続いて上記のように書かれている。逆に非グローバルでロール固有のプロパティのみを持つ場合、そのままpresentation
ロールとなる、ということだが、グローバルプロパティもってないんだからそりゃそうでしょ、といった説明だ[3]。
Presentational Childrenの要素はロールが無意味
ロールにはChildren Presentationalという性質をもち、いくつかのロールはこれがtrue
となっている。
このPresentational Childrenがどういうものかというと、たとえばbutton
ロールのChildren Presentationalの性質はtrue
で、このロールの子孫要素はアクセシビリティツリーに公開されない、というもの。一見不思議に思うかもしれないが、button
ロールはアクセシブルな名前をコンテンツから計算するので、中身自体がツリーから消えても問題ない。
<button>何かしらのテキスト</button>
<!--
button要素の計算されたアクセシビリティプロパティは
Role: button
name: 何かしらのテキスト (👈 アクセシブルな名前をしっかり持っている)
focusable: true
-->
具体的にこれの何がロールに影響を与えるかというと、button
要素にテキストだけでなく要素を入れるとわかりやすい。
<button>
<img
src="path/to"
alt="何かしらのテキスト"
/><!-- img要素はアクセシビリティツリーに公開されない -->
</button>
<!--
ただし、button要素の計算されたアクセシビリティプロパティは
Role: button
name: 何かしらのテキスト (👈 計算されてalt属性から抽出される)
focusable: true
-->
このようにimg
要素だろうがなんだろうが、button
ロールをもつ要素に何を入れても公開されない。
つまり、button
要素の中で明示的にロールを指定したりしても、機能しないということだ。
<button>
<div role="alert">メッセージ</div>
<!-- div要素はアクセシビリティツリーに公開されない -->
</button>
<!--
ただし、button要素の計算されたアクセシビリティプロパティは
Role: button
name: メッセージ (👈 計算されてdivのコンテンツから抽出される)
focusable: true
-->
ブラウザと支援技術の確認を忘れずに
以上が把握している限りのロールの決まり方なのだけど、これはあくまでも仕様の話。実際はブラウザの実装次第な部分は多く、さらにそのブラウザと支援技術との組み合わせでも挙動は変わるので、そのマークアップが期待通りかどうかは実際に動作させてみて確認するしかない。
最後に
なぜ、この記事を書いたかというと、もちろんWAI-ARIAの理解を深めるためもそうだけど、MarkuplintのWAI-ARIAのルールを作る上でこのあたりの調査をしたからだ。まだv2.x
では中途半端な実装しかできていなかったが、近日リリース予定のv3.0.0
ではこのあたりをかなり強化した。すでにv3.0.0-rc
はプレリリースしていて試すことができるし、また、興味があればどういった実装をしたのかソースコード見てもらえると嬉しい。この記事に書いたことのほとんどがJavaScriptで書かれている。
つまり、この記事の変なところがあった場合にご指摘いただけるとMarkuplintも改善される、という寸法です。ご協力よろしくお願いします!
Discussion