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

HTML, Accessibility

淡々と 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 ロールを付けて表現することがある。

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>

articleのヘッダー部分、という位置付けで header が使われるケースもある。
役割的に意味はないけど、divを使うよりは見通しが良いかも。

html

<body>
  <!-- banner ロールが付くのはここ -->
  <header>
    <h1>ホームページ</h1>
  </header>
  <main>
    <article>
      <!-- ここは banner ではないが、問題なし -->
      <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 ロールを持った要素の中で、別のものとして扱える。

おわり

つづく