WAI-ARIA はじめの一歩。サンプルソースで学ぶ WAI-ARIA 導入方法

WAI-ARIA をマークアップに取り入れるはじめの一歩として、まずは WAI-ARIA を既存のマークアップに取り入れる際に、扱いやすい項目についてピックアップしつつ、それぞれのサンプルソースを挙げながら紹介します。

この記事は、Web Accessibility Advent Calendar 2013、4日目の記事です。ベタな内容なのでもしかすると他の方とかぶるかもしれませんが、気にせずいきます。

とはいっても、WAI-ARIA の仕様をすべて詳細に取り上げるのは大変なので、まずは WAI-ARIA を既存のマークアップに取り入れる際に、扱いやすい項目についてピックアップしつつ、それぞれのサンプルソースを挙げながら、紹介してみたいと思います。まだ WAI-ARIA をマークアップに取り入れたことがない方が、初めて使うきっかけになれば幸いです。

WAI-ARIA の仕様は下記にあります。

現在、勧告候補 (Candidate Recommendation) ですが、仕様策定の計画的には来年、2014年の始めに勧告案 (Proposed Recommendation) へと進み、2014年中には勧告される予定です。また、今年の 9月には WAI-ARIA 1.1 の草案 が公開されました。

その他、関連仕様として、

があり、これら仕様書の日本語訳 (最新の仕様とは異なる場合もありますので注意) を日立製作所さんが自社のサイト上で公開してくれていますので、そちらを参考にしてみるものよいでしょう。

ちなみに、WAI-ARIA の WAI は 「Web Accessibility Initiative」 の略で、W3C 内でアクセシビリティ関連のガイドライン策定等を担当する組織です。ARIA は 「Accessible Rich Internet Applications」 の略で、「リッチなインターネットアプリケーションをアクセシブルにするための仕様」 ということになります。

「リッチな」 ということで、HTML のみで記述された Web コンテンツだけでなく、JavaScript や Flash などを使用した Web コンテンツや Web アプリケーションをアクセシブルにすることを念頭に置いた仕様群となっています。

WAI-ARIA の機能・役割は?

WAI-ARIA 仕様の主な機能は大きく下記の 2つです。

  1. コンテンツの役割、状態、特性を意味づけし、ユーザエージェントに伝える
  2. tabindex 属性を拡張し、すべての要素に対するキーボードナビゲーションとフォーカス管理を実現

今回の記事では、1番目の機能である、意味づけについて触れています。この辺の WAI-ARIA とは? 的な話を書き出すと長くなるので、その辺は下記のリンク先などを参照してください (手抜きでは...... ない)。

意味づけ的な用途で言えば、所謂、構造化データをマークアップするための Microdata、Microformats、RDFa などがありますし、一部で意味的にかぶっているものもありますが、これらを上手に組み合わせることで、アクセシブルで、マシンリーダブルな Web コンテンツを実現することが可能です。

なお、余談ですが Microdata に関しては下記のエントリーも参考にしてみてください。

ロール (role)、ステート (state)、プロパティ (property)

さて、実際に role についてここから解説していきます。が、その前に、WAI-ARIA では、ロール (role)、ステート (state)、プロパティ (property) という 3つの分類を覚えておく必要があります。

簡単に言うとそれぞれ下記のような用途があります。

  • ロール (role) : 役割を定義する
  • ステート (state) : オブジェクトに関する特定の情報を提供する (状態を伝える)
  • プロパティ (property) : オブジェクトに関する特定の情報を提供する

ロール (role)

役割を定義する role っていうのはわかりやすいと思います。このオブジェクトはナビゲーションですよとか、そういう役割を role 属性とその値によって定義していきます。今回はこれを中心に書きます。詳細は後述。

ステート (state)

状態を伝える state っていうのは、例えばそのオブジェクトが今どういう状態か、例えば展開、折り畳み可能な要素グループが、現在展開されているのか、それとも折り畳まれているかといった状態や、ある要素がユーザーに対して表示されている状態か否かみたいなことです。

WAI-ARIA 1.0 では、下記の state が定義されています。すべて aria-** という形式の属性を使用します。

  • aria-busy
  • aria-checked
  • aria-disabled
  • aria-dropeffect
  • aria-expanded
  • aria-grabbed
  • aria-hidden
  • aria-invalid
  • aria-pressed
  • aria-selected

値は、true / false のように、真偽値が入る場合が多く、その時の状況に応じて値が変化するのが通常です。

プロパティ (property)

property も state 同様にオブジェクトに関する特定の情報を提供するんですが、state と少し違うのはある操作によって起こった状態の変化を伝えるというよりも、その要素がどういう特性をもつのかを伝えます。

例えばある要素が、階層構造になったある要素ブロックの中でどのレベルにあるのかとか、このリストはユーザーが複数の項目を選択できるリストですよとか。

WAI-ARIA 1.0 では、下記の property が定義されています。すべて aria-** という形式の属性を使用します。

  • aria-activedescendant
  • aria-atomic
  • aria-autocomplete
  • aria-controls
  • aria-describedby
  • aria-flowto
  • aria-haspopup
  • aria-label
  • aria-labelledby
  • aria-level
  • aria-live
  • aria-multiline
  • aria-multiselectable
  • aria-orientation
  • aria-owns
  • aria-posinset
  • aria-readonly
  • aria-relevant
  • aria-required
  • aria-setsize
  • aria-sort
  • aria-valuemax
  • aria-valuemin
  • aria-valuenow
  • aria-valuetext

こちらは、aria-activedescendant (フォーカスが置かれている要素内でアクティブになっている子要素の id 属性値を指定) のような例外はありますが、基本的には初期に指定した値が頻繁に変更されることは少ないと思います。

また、WAI-ARIA 1.1 では、下記のプロパティが追加定義されています。

  • aria-describedat

aria-describedat は、HTML でいうところの longdesc 属性と同じ役割を果たします。こちらも余談になりますが、longdesc 属性については下記のエントリーも参考まで。

role を使ってみよう

前置きが長くなりましたが、実際に role を使用してみます。前述の通り、role は、ある要素、もしくは要素ブロックの役割を定義します。

role は、下記の 4つに分類されます。

  1. Abstract Roles
  2. Widget Roles
  3. Document Structure Roles
  4. Landmark Roles

Abstract Roles

抽象的な role。これはとりあえず考えなくていいです。スルー。

  • command
  • composite
  • input
  • landmark
  • range
  • roletype
  • section
  • sectionhead
  • select
  • structure
  • widget
  • window

Widget Roles

WAI-ARIA における role は、大きく、ユーザインタフェースウィジェット (スライダー、ツリーコントロールなど) を定義する role とページ構造を定義する role に分類されています。

ウィジェットとは、簡単に言えば何らかの操作を提供したり、操作の結果として表示されるオブジェクトを指します。ボタンとか、ダイアログとか、またはそれらを組み合わせたものですね。

よく使う UI としてタブ型のインタフェースがありますが、サンプルとして tab、tablist、tabpanel を使用したマークアップ例を後述します。

  • alert
  • alertdialog
  • button
  • checkbox
  • dialog
  • gridcell
  • link
  • log
  • marquee
  • menuitem
  • menuitemcheckbox
  • menuitemradio
  • option
  • progressbar
  • radio
  • scrollbar
  • slider
  • spinbutton
  • status
  • tab
  • tabpanel
  • textbox
  • timer
  • tooltip
  • treeitem

Widget Roles の中でも、上記の role は、スタンドアロンのユーザインタフェースウィジェットとして、下記の複合ユーザインタフェースの一部として機能します。

一方で下記の role は、複合ユーザインタフェースウィジェットとして動作します。

  • combobox
  • grid
  • listbox
  • menu
  • menubar
  • radiogroup
  • tablist
  • tree
  • treegrid

Document Structure Roles

この辺は HTML に慣れた人であればわかりやすいと思いますが、文書構造を定義するための role になります。今回は長くなるのでスルー。

  • article
  • columnheader
  • definition
  • directory
  • document
  • group
  • heading
  • img
  • list
  • listitem
  • math
  • note
  • presentation
  • region
  • row
  • rowheader
  • separator
  • toolbar

Landmark Roles

Document Structure Roles 同様、文書構造を定義しますが、その中でも特別な意味を持つ領域を示します。こいつはかなり利用頻度が高いですし、この Blog でも使用していますが、まずは Landmark Roles の指定からはじめてみると入りやすいんじゃないでしょうか。ということでこれの使い方を次にサンプルを交えて紹介していきます。

  • application
  • banner
  • complementary
  • contentinfo
  • form
  • main
  • navigation
  • search

具体的なサンプルソース

では具体的にサンプルソースを挙げていきます。

タブ型 UI での利用例

最初に Widget Roles を使用した例を挙げてみます。前述したとおり、よく使われがちな 「タブ」 を操作することによって、表示されるコンテンツが切り替わるあれについて WAI-ARIA を導入するとどうなるかをやってみましょう。

まずはベースとなる HTML から。

<div class="tab-block">
  <ul>
    <li><a href="#tab-content01">タブ1</a></li>
    <li><a href="#tab-content02">タブ2</a></li>
    <li><a href="#tab-content03">タブ3</a></li>
  </ul>
  <div id="tab-content01">
   <!-- 内容 -->
  </div>
  <div id="tab-content02">
   <!-- 内容 -->
  </div>
  <div id="tab-content03">
   <!-- 内容 -->
  </div>
<div>

特に珍しくもない感じだと思いますが、JavaScript でタブの切り替えを実装したりすればよく見る感じのタブ型 UI になると思います。

ただ、このままだと、支援技術を利用して閲覧している場合、上部のリストがタブ切り替えのためのリストだと言うことや、それを操作したことで、続く div 要素がどのような状態になったのかがわかりません。何かよくわからんアンカーリンクのリストが出てきて、クリックしてみたけど何も起こらない...... っていう状態になります。

そこで、リストがタブのリストなんだよということや、そのタブと続く div 要素がどのように対応していて、どの div 要素が表示されているのかといった情報を、WAI-ARIA を使用して定義してみます。

ここでは、tab、tablist、tabpanel を使用してみます。下記のような感じ。

<div class="tab-block">
  <ul role="tablist">
    <li><a href="#tab-content01" role="tab">タブ1</a></li>
    <li><a href="#tab-content02" role="tab">タブ2</a></li>
    <li><a href="#tab-content03" role="tab">タブ3</a></li>
  </ul>
  <div id="tab-content01" role="tabpanel">
   <!-- 内容 -->
  </div>
  <div id="tab-content02" role="tabpanel">
   <!-- 内容 -->
  </div>
  <div id="tab-content03" role="tabpanel">
   <!-- 内容 -->
  </div>
<div>
tab

tabpanel の見出し、つまり、タブとなっている要素を示します。上の例だとリスト内の各リンクですね。

tablist

tab のリスト、つまり、タブを内包する要素です。

tabpanel

tab に関連付けられたリソースのコンテナ、つまり、タブで切り替えた時に表示されるコンテンツが含まれている要素ブロックはこれですよという情報を伝えます。

ここまでは簡単ですね。ただし、まだ各要素の役割を伝えただけでこれだけでは不足です。タブ切り替えによる状態の変化については、これでは伝わりませんので、ここで先に簡単に紹介した state と property を使用して、それら情報を伝えられるようにします。

下記の 3つを使ってみましょう。

  • aria-selected (state)
  • aria-hidden (state)
  • aria-controls (property)
aria-selected (state)

オブジェクトが選択されているかどうか。値は真偽値 (true / false)。

aria-hidden (state)

オブジェクトがユーザーから見えるかどうか。値は真偽値 (true / false)。

aria-controls (property)

これが指定された要素によって制御される要素を定義します。値は制御対象となる要素の id 属性値です。

適用すると下記のような感じになります。

<div class="tab-block">
  <ul role="tablist">
    <li><a href="#tab-content01" role="tab" aria-selected="true" aria-controls="tab-content01">タブ1</a></li>
    <li><a href="#tab-content02" role="tab" aria-selected="false" aria-controls="tab-content02">タブ2</a></li>
    <li><a href="#tab-content03" role="tab" aria-selected="false" aria-controls="tab-content03">タブ3</a></li>
  </ul>
  <div id="tab-content01" role="tabpanel" aria-hidden="false">
   <!-- 内容 -->
  </div>
  <div id="tab-content02" role="tabpanel" aria-hidden="true">
   <!-- 内容 -->
  </div>
  <div id="tab-content03" role="tabpanel" aria-hidden="true">
   <!-- 内容 -->
  </div>
<div>

aria-hidden、aria-selected の値は、タブの切り替えにあわせて変更する必要があります。JavaScript を使用してタブ切り替えを実装するなら大して難しくはないですね。

ついでに、CSS 側も、WAI-ARIA の値をセレクタに使用して適用するとわかりやすくなります。適当ですけど下記のように。

[aria-hidden="true"] {
  visibility: hidden;
}
[aria-hidden="false"] {
  visibility: visible;
}
[aria-selected="true"] {
  background-color: red;
}

ついでに、aria-labelledby (property) を指定して、tabpanel と見出しを関連付ける例も挙げてみます。下記のような感じ。

<div class="tab-block">
  <ul role="tablist">
    <li><a id="tab-content01-title" href="#tab-content01" role="tab" aria-selected="true" aria-controls="tab-content01">タブ1</a></li>
    <li><a id="tab-content02-title" href="#tab-content02" role="tab" aria-selected="false" aria-controls="tab-content02">タブ2</a></li>
    <li><a id="tab-content02-title" href="#tab-content03" role="tab" aria-selected="false" aria-controls="tab-content03">タブ3</a></li>
  </ul>
  <div id="tab-content01" role="tabpanel" aria-hidden="false" aria-labelledby="tab-content01-title">
   <!-- 内容 -->
  </div>
  <div id="tab-content02" role="tabpanel" aria-hidden="true" aria-labelledby="tab-content02-title">
   <!-- 内容 -->
  </div>
  <div id="tab-content03" role="tabpanel" aria-hidden="true" aria-labelledby="tab-content03-title">
   <!-- 内容 -->
  </div>
<div>

ランドマーク role の利用例

次に Landmark Roles の導入例を挙げます。今日からでもご自身の Web サイトに適用してみてください。

Landmark Roles に分類されている role を簡単に説明すると下記のようになります。

application

閲覧しているコンテンツがアプリケーションであると定義します。これが宣言されたコンテンツで、支援技術は閲覧モードを文書閲覧モードからアプリケーションモードに切り替えます。この role はコンテンツのルートノード (HTML でいえば、body 要素) に指定する必要があります。

role="application" が指定された body 要素内に、文書閲覧モードが適したオブジェクトがある場合は、そのオブジェクトに対して role="document" することもできます。

banner

主な見出しや文書内のタイトルを含む領域を定義します。例えば Web サイトのロゴなど、単体のページではなく、Web サイト全体に共通する見出し部分です。この Blog の場合は、下記のように使用しています。

<header>
 <div id="header">
   <div class="inner">
    <div id="banner" role="banner">
     <h1 class="siteTitle" itemprop="name"><a href="/"><img src="/static/img/share/w3w_logo.png" alt="WWW WATCH"></a></h1>
     <p class="description" itemprop="description">Blog の概要文</p>
    </div>
    <div id="mainNav">
     ...省略...
    </div>
   </div>
   ...省略...
</header>
complementary

文書内で主要なコンテンツかつ、文書からその部分だけを抜き出したとしても意味を持つセクションを定義します。あんまり使うシチュエーションが思い浮かばないですけども...... 仕様書では例として、番組表とか、天気予報とかが挙げられています。

contentinfo

脚注や著作権の表記など、文書に関する情報を含む領域を定義します。簡単に言い切ってしまえば、footer 要素です。

<footer role="contentinfo">
 <address>
  このサイトに関するお問い合わせ先:
  <a href="mailto:japan@example.com">日本太郎</a>
 </address>
 <p><small>© copyright 2013 Example Inc.</small></p>
</footer>
form

そのまま、フォーム領域ですが、通常は form 要素でマークアップすればいいので多分使わない。

main

文書内で主要な領域を定義します。HTML 5.1 で追加された main 要素と同じ。

HTML5 仕様書の方で、main 要素は WAI-ARIAの role="main" にマッピングされるけど、ユーザエージェントがこのマッピングを実装するまでは、main 要素と role="main" を併用した方がいいよと書かれています。実際に使用すると下記のような感じですね。

<body>
 <header>
  <h1>俺の日記</h1>
  <p>俺の日記です。</p>
 </header>
 <main role="main">
  <article>
   <h1>お米食べろ</h1>
   <p>日本人ならやっぱお米でしょ。</p>
  </article>
  <article>
   <h1>ゼリー食べろ</h1>
   <p>デザートと言えばやっぱゼリーでしょ。</p>
  </article>
  <article>
   <h1>現実逃避</h1>
   <p>寝てるだけでお金もらえねーかなー</p>
  </article>
 </main>
 <footer>Copyright 2013 俺</footer>
</body>
navigation

ページ内のナビゲーション領域を定義します。HTML5 では nav 要素がありますので、nav 要素のみ、もしくは nav 要素と role="navigation" でいいんですが、nav 要素は基本的に 「主要なナビゲーション」 のみで使えと仕様書の方では書かれていますので、それ以外のナビゲーション的な扱いとなるリンクの集まりもこれで定義してあげるといいと思います。

<div class="pagination">
 <h1 id="pagination-title">ページ送りナビゲーション</h1>
 <ul role="navigation" aria-describedby="pagination-title">
  <li><span>1</span></li>
  <li><a href="/2">2</a></li>
  <li><a href="/3">3</a></li>
  <li><a href="/last">最後のページ</a></li>
 </ul>
</div>
search

これも読んで字のごとしですね。検索機能を提供する領域ですよと定義します。

<form method="get" action="/search" role="search">
 <fieldset>
  <legend accesskey="s">サイト内検索</legend>
  <input type="search" name="q" value="" placeholder="検索ワードを入力" tabindex="1" accesskey="1" />
  <input type="submit" name="sa" value="Search" tabindex="2" accesskey="2" />
 </fieldset>
</form>

どうでしょ? ランドマークに分類される role の導入はそんなに難しくなさそうですよね。

なお、マークアップ例は当たり前のように HTML5 で書いていますが、これは WAI-ARIA を導入するに当たって HTML5 が最も楽だからです。例えば、(今回は触れていませんが、フォーカス管理に関連する) tabindex 属性は HTML 4.01 や XHTML 1.x では使用できる要素が限られていますし、WAI-ARIA の state や property は invalid な属性になってしまいます (個人的にはアクセシビリティのために正しく使われているのであれば、invalid でも気にしなくていいと思いますが)。

その点、HTML5 では tabindex 属性はすべての要素に指定可能ですし、WAI-ARIA も HTML5 においては問題なく利用可能です。

ただし、HTML5 で定義されたセマンティックな要素に対して role 属性を使用する場合は要素の意味と role 属性値がきちんと整合するように注意しましょう。極端な例ですが、main 要素に role="navigation" なんてやってはいけません。

WAI-ARIA の入門書的位置づけとなる仕様、「WAI-ARIA 1.0 Primer」 には、第 3 章に下記のような記述があります。

  1. Use native markup when possible. (可能なかぎりネイティブのマークアップを使用する)
  2. Apply the appropriate roles. (適切な role を適用する)
  3. Preserve semantic structure. (セマンティクスの構造を守る)
  4. Build relationships. (関係を構築する)
  5. Set states and properties in response to events. (イベントへの応答としてプロパティを設定する)
  6. Support full, usable keyboard navigation. (使いやすい完全なキーボードナビゲーションをサポートする)
  7. Synchronize the visual interface with the accessible interface. (視覚的なインタフェースとアクセシブルなインタフェースを同期する)

3. Building Accessible Applications with WAI-ARIA : WAI-ARIA 1.0 Primer から引用

Web ページ (文書) なのか、Web アプリケーションなのかによって、HTML の要素、属性を使うのか、role を使うのかは判断が分かれる場合もあると思いますが、基本的なステップとして覚えておくといいと思います。


ということで、もっと簡単にサンプルソース挙げてとか思って書き始めたら、思ったよりも長くなってごっそり説明を削ったり、肝心のサンプルソースが思ったより分量がなかったっていう微妙な感じになったりしておりますが、この辺で次の方にバトンを渡したいと思います。

最後に、アクセシビリティは Web コンテンツにおいて最も重要、かつ基本的な要件です。たまに Web アクセシビリティを 「おもてなし」 とか 「気遣い」 とかいう言葉で表現する方がいますが、そんな表面的な概念ではなく、情報にアクセスできるか否かというのはもっと根本的な課題です。アクセスできないコンテンツに価値はないんですから。

また、アクセシビリティというと、とかく障がいを持った方々向けに云々という認識になりがちです。確かに支援技術を使うユーザーは全体の一部 (でも実は読み上げ機能とかは障がいのあるなしに関係なく今後普通に使う機会が増えると思うよ) かもしれませんが、アクセシビリティの確保はそういう一部の人に対する配慮で対応するものではなく、「すべてのユーザーが情報にアクセスできる」 という Web コンテンツにとって最も基本的なことを実現するために当たり前のことと考える必要があると思います。

そんな中で、WAI-ARIA は年々複雑化していく Web コンテンツに対するアクセシビリティを確保するための重要な仕様として存在していますので、フロントエンド周りに興味をお持ちの方は、あわせて興味の対象にしてみると面白いと思いますけども。

記事をここまで御覧頂きありがとうございます。
この記事が気に入ったらサポートしてみませんか?