淡々とWAI-ARIAについて書く1

淡々と WAI-ARIA の role や state についてまとめます。 WAI-ARIA 1.2 を対象にしています。

role="heading"

https://www.w3.org/TR/wai-aria-1.2/#heading
文書の構造に関わる、「文書構造ロール」のひとつです。
heading ロールは見出しを表現します。対応する要素は h1 から h6 です。
レベルは aria-level 属性で設定します。1 以上の整数で指定し、初期値は 2 です。

html
<div role="heading" aria-level="1">見出しレベル1</div>

HTML の要素にない特徴として、7 以降のレベルを設定できます。
上限については調べていませんが、VoiceOver は 9 までを認識しました。
HTML との整合性を保つため、7 以降を設定するのは避けるべきとされています。
(MDN の Accessibility Concerns より)

html
<div role="heading" aria-level="9">見出しレベル9</div>

h1から h6 のスタイルを避けるため、この role を使って見出しコンポーネントを作るケースもあります。
(それを推奨しているわけではありません)

tsx
import * as React from 'react'

type Props = {
  level: number
}

export const HeadingComponent: React.FC<Props> = props => {
  return (
    <div role="heading" aria-level={props.level}>
      {props.children}
    </div>
  )
}

hgroupの子要素にもなれるはずですが、hgroupあたりの実装がされていないので詳細は不明です。
(セクショニングや hgroup については闇の歴史があり、別の記事でまとめます)

role="button"

https://www.w3.org/TR/wai-aria-1.2/#button
ボタンであることを示します。対応する要素は button です。

html
<div role="button" tabindex="0">ボタンだよ</div>

buttonは対話型コンテンツ(interactive content)のひとつで、フォーカス可能です。
しかし div はフォーカス可能ではないので、tabindex属性と共に用いることが望ましいとされます。
また、ロールは本来の button 要素が持つ、Enter キーや Space キーを押下した時の挙動を再現しません。
キーボードイベントもハンドラに追加する必要があり、お取り扱いには注意が必要です。

tsx
import * as React from 'react'

type Props = {
  handleClick: () => void
}

export const ButtonComponent: React.FC<Props> = props => {
  const onKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {

      // Enter か Space キーが押されているかチェック
      if (event.key === 'Enter' || event.key === ' ') {
        props.handleClick()
      }
    },
    [props],
  )
  return (
    <div
      role="button"
      tabindex="0"
      onClick={props.handleClick}
      onKeyDown={onKeyDown}
    >
      ボタンだよ
    </div>
  )
}

キーを押して props.handleClick() を発火させるのは変ですが…キー操作は mousedownmouseup を模倣し、それらの action で発火するものが click なので、あながち間違とは言えません。(そう信じています)

本来の button にない利点は、キーボードでボタンを押す時の奇妙な振る舞いを再現しないことです。
Chrome では、ボタン上でSpaceキーを押すと keyPress 時にイベントが発火しますが、Firefox では keyUp で発火します。なぜこの仕様なのかは、GUI の歴史から追っていますが、把握できていません…。

また、button特有のスタイルからも解放されます。
例えば、react-aria-menubuttonは、開発者が CSS でスタイルを設定をしやすいように、この role で button を表現できます。

role="main"

https://www.w3.org/TR/wai-aria-1.2/#main
ランドマークロールのひとつです。ランドマークは特定のコンテンツの場所を示すもので、支援技術のナビゲートを助ける役割を持ちます。
main ロールは主要なコンテンツの場所であることを示します。対応する要素は main です。

html
<main role="main"></main>

通常、main要素には暗黙的な main ロールが与えられるので、わざわざ同じロールを付ける必要はありません…が、 IE は main 要素に対応しておらず、対応させるために同じ role の main ロールを与えるハックをやっています。
このように、後方互換性を保つため二重のロールが与えられているケースもあり、闇を感じさせてくれます。

そもそも IE はこれを block ではなく inline な要素として解釈するので、余計な display: block; を避けるためにも、main要素を使わず、div要素に main ロールを付けて表現することがあります。
このブログも div 要素に main ロールを使用しています。(と思ったらいつの間にか消えていました)

html
<div role="main"></div>

main ロールは 1 ページに 1 つでなければいけませんが、hidden属性があるものは無視されるので、動的に切り替える目的であれば複数記述できます。

html
<div role="main"></div>

// hidden 属性があるので認識されない
<div role="main" hidden></div>

role="banner"

https://www.w3.org/TR/wai-aria-1.2/#banner
ランドマークロールのひとつです。
ページの上の方にあって、サイト名とかコンテンツの導入的な部分を示します。
関連する要素が header です。

html
<body>
  <header></header>
</body>
html
<body>
  <div role="banner"></div>
</body>

body要素の直下にない banner ロールは役割を持ちません。
HTML Accessibility API Mappings 1.0 では、bodyの中とそうでない場合で明確にロールを分けています。
例えば、main, article, sectionなどの要素内に記述された header 要素は、ロールを持っていません。

html
<body>
  <header><h1>ヘッダー</h1></header>
  <main>
    <!-- これは問題ないが、bannerとしての役割がない -->
    <header><h2>メインのヘッダー</h2></header>
  </main>
</body>

上記も踏まえて、ページ内で認識できる banner ロールは基本的に 1 つです。
ただし、document ロールや application ロールがそれぞれの banner ロールを持つのは可能です。
(document ロールと application ロールについては次の項目で書きます)

html
<body>
  <header id="header">
    <h1>ヘッダー</h1>
  </header>
  <div id="nestedDocument" role="document" tabIndex="0">
    <header id="nestedDocumentHeader">
      <h2>ネストされたドキュメントのヘッダー</h1>
    </header>
  </div>
</body>

banner ロールの説明からは外れますが、articleのヘッダー部分、という位置付けで header が使われるケースもあります。
役割的に意味はないですが、divを使うよりは見通しが良いのかもしれません。

html
<body>
  <!-- banner ロールが付くのはここ -->
  <header>
    <h1>ホームページ</h1>
  </header>
  <main>
    <article>
      <!-- ここは付かないが、問題なし -->
      <header>
        <h2>Article です</h2>
      </header>
      <section>
        <h3>Section です</h3>
        <p>p です</p>
      </section>
    </article>
  </main>
</body>

role="document" と role="application"

banner ロールの例に出てきたやつらで、それぞれが文書構造ロールのひとつです。
これらのロールは、支援技術の読み取りに関係します。

https://www.w3.org/TR/wai-aria-1.2/#document
document ロールは、支援技術に読み取りをさせたいコンテンツであることを示します。
要するに対応する要素は Web ページそのもので、デフォルトで読み取りに対応しています。
その中でもフォーカスさせたいテキストがある時に、tabindexを併用して使われることがあります。

html
<div id="mydocument" role="document" tabindex="0">
  <p>俺の声が聞こえるか</p>
</div>

https://www.w3.org/TR/wai-aria-1.2/#application
一方 application ロールは、支援技術の入力をアプリケーションに伝えることを示します。
その名前から誤解されがちですが、ページがアプリケーションであることを示すものではありません。
buttoninput などの対話型コンテンツ、HTML や WAI-ARIA だけでは表現しきれない、多様な操作を求めるコンポーネントに対して使用するものです。
例えば、WAI-ARIA 1.2 での説明では、HTML で表現することが難しい「スライド作成ツール」を挙げていて、実際にGoogle Slideは、body要素で application ロールを使用しています。

application は、内部にフォーカス可能な要素が必ず 1 つはあるべきとされます。
また、フォーカス可能な要素を管理するため aria-activedescendant 属性などを併用する必要があります。
aria-activedescendant は他の記事で触れますが、フォーカスされている要素の id を指定するものです。

html
<!-- 例としては良くないものです -->
<div id="myapp" role="application" tabindex="0">
  <div role="banner">何らかのアプリケーション</div>
  <div role="toolbar" aria-activedescendant="button02" tabindex="0">
    <div id="button01" role="button" aria-pressed="false" tabindex="0">
      その1
    </div>
    <div id="button02" role="button" aria-pressed="true" tabindex="0">
      その2
    </div>
    <div id="button03" role="button" aria-pressed="false" tabindex="0">
      その3
    </div>
  </div>
</div>

Google レベルのデスクトップアプリケーション的な UI を構築する時に、はじめて考えるものかもしれません。
操作を一手に担うため、使うには大いなる責任が伴います。

role="navigation"

https://www.w3.org/TR/wai-aria-1.2/#navigation
ランドマークロールのひとつです。
ページのナビゲーション(リンク集など)が入る部分で、対応する要素は nav です。
aria-labelaria-labelledby 属性を設定すると、それがナビゲーションの説明になります。
例えばスクリーンリーダーは、下記を サンプルページへのリンク集, ナビゲーション のように読み上げます。

html
<!-- aria-labelが説明になる -->
<nav aria-label="サンプルページへのリンク集">
  <ul>
    <li><a href="./knowheading/">headingを知る</a></li>
    <li><a href="./knowbanner/">bannerを知る</a></li>
    <li><a href="./knowheading/">headingを知る</a></li>
  </ul>
</nav>

<!-- 中に見出しなどがある場合、aria-labelledbyを使ってidと紐づけられる -->
<nav aria-labelledby="navigationHeading">
  <h2 id="navigationHeading">サンプルページへのリンク集</h2>
  <ul>
    <li><a href="./knowheading/">headingを知る</a></li>
    <li><a href="./knowbanner/">bannerを知る</a></li>
    <li><a href="./knowheading/">headingを知る</a></li>
  </ul>
</nav>

navigation ロールは 1 つのページに複数置けます。それぞれに説明があると良さそうです。

role="contentinfo"

https://www.w3.org/TR/wai-aria-1.2/#contentinfo
ランドマークロールのひとつです。
著作者など、ドキュメントの情報を示す時に使われます。 対応する要素は footer です。

html
<div role="contentinfo">フッターです</div>
<footer>同じくフッターです</footer>

こちらも banner と全く同じで基本 1 つ、bodyの子でない footer にロールはありません。
また document ロールや application ロールを持った要素の中で、別のものとして扱えます。

おわり

次回はフォーム系について書きます。